POE2 Trade ST工具箱

自动转换简繁中文(页面转简体,输入转繁体)- stomtian

  1. // ==UserScript==
  2. // @name POE2 Trade ST工具箱
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.3.2
  5. // @description 自动转换简繁中文(页面转简体,输入转繁体)- stomtian
  6. // @author stomtian
  7. // @match https://www.pathofexile.com/trade*
  8. // @match https://pathofexile.com/trade*
  9. // @grant GM_getValue
  10. // @grant GM_setValue
  11. // @grant unsafeWindow
  12. // @license MIT
  13. // @require https://cdn.jsdelivr.net/npm/opencc-js@1.0.5/dist/umd/full.min.js
  14. // @run-at document-end
  15. // @noframes true
  16. // ==/UserScript==
  17. (function() {
  18. 'use strict';
  19. console.log('POE2 Trade ST工具箱已加载');
  20. const STATE = {
  21. pageSimplified: GM_getValue('pageSimplified', true),
  22. inputTraditional: GM_getValue('inputTraditional', true),
  23. originalTexts: new WeakMap(),
  24. configs: GM_getValue('savedConfigs', {}), // 保存的配置
  25. autoLoadEnabled: GM_getValue('autoLoadEnabled', false), // 自动加载开关
  26. matchedCards: [], // 添加匹配的卡片列表
  27. currentMatchIndex: -1, // 添加当前匹配索引
  28. showOnlyMatched: GM_getValue('showOnlyMatched', false), // 添加新的状态
  29. searchPresets: GM_getValue('searchPresets', {}) // 添加预设关键词存储
  30. };
  31. const CUSTOM_DICT = [
  32. ['回覆', '回復'],
  33. ['恢覆', '恢復'],
  34. ];
  35. const CONFIG = {
  36. maxAttempts: 50,
  37. checkInterval: 100,
  38. inputSelector: 'input[type="text"]:not(#config-name):not(#config-category), textarea',
  39. textSelector: '.search-bar, .search-advanced-pane, .results-container, .resultset',
  40. excludeSelector: 'script, style, input, textarea, select, .converter-controls'
  41. };
  42. function waitForElement(selector) {
  43. /**
  44. * 等待指定选择器的元素出现在页面上
  45. * @param {string} selector - CSS选择器
  46. * @returns {Promise} - 当元素出现时解析的Promise
  47. */
  48. return new Promise(resolve => {
  49. // 如果元素已经存在,立即解析
  50. if (document.querySelector(selector)) {
  51. resolve();
  52. return;
  53. }
  54. // 创建观察器监听DOM变化
  55. const observer = new MutationObserver(() => {
  56. try {
  57. if (document.querySelector(selector)) {
  58. observer.disconnect();
  59. resolve();
  60. }
  61. } catch (error) {
  62. console.debug('等待元素时出错:', error);
  63. }
  64. });
  65. // 开始观察DOM变化
  66. observer.observe(document.body, {
  67. childList: true,
  68. subtree: true
  69. });
  70. });
  71. }
  72. function waitForOpenCC() {
  73. /**
  74. * 等待OpenCC库加载完成
  75. * @returns {Promise} - 当OpenCC可用时解析的Promise
  76. */
  77. return new Promise((resolve, reject) => {
  78. // 如果OpenCC已经加载,立即解析
  79. if (typeof window.OpenCC !== 'undefined') {
  80. resolve(window.OpenCC);
  81. return;
  82. }
  83. let attempts = 0;
  84. const maxAttempts = CONFIG.maxAttempts;
  85. const checkInterval = CONFIG.checkInterval;
  86.  
  87. // 定期检查OpenCC是否已加载
  88. const checkOpenCC = setInterval(() => {
  89. attempts++;
  90.  
  91. if (typeof window.OpenCC !== 'undefined') {
  92. clearInterval(checkOpenCC);
  93. resolve(window.OpenCC);
  94. return;
  95. }
  96. // 超过最大尝试次数后放弃
  97. if (attempts >= maxAttempts) {
  98. clearInterval(checkOpenCC);
  99. reject(new Error('加载OpenCC超时'));
  100. }
  101. }, checkInterval);
  102. });
  103. }
  104. function createConverters(OpenCC) {
  105. /**
  106. * 创建简繁转换器
  107. * @param {Object} OpenCC - OpenCC库对象
  108. * @returns {Object} - 包含简体到繁体和繁体到简体的转换器
  109. */
  110. try {
  111. // 创建简体到繁体的转换器(输入转换用)
  112. const toTraditional = OpenCC.ConverterFactory(
  113. OpenCC.Locale.from.cn,
  114. OpenCC.Locale.to.tw.concat([CUSTOM_DICT])
  115. );
  116. // 创建繁体到简体的转换器(页面转换用)
  117. const toSimplified = OpenCC.ConverterFactory(
  118. OpenCC.Locale.from.tw,
  119. OpenCC.Locale.to.cn
  120. );
  121. return { toTraditional, toSimplified };
  122. } catch (error) {
  123. console.error('创建转换器时出错:', error);
  124. // 返回空转换器,避免脚本崩溃
  125. return {
  126. toTraditional: text => text,
  127. toSimplified: text => text
  128. };
  129. }
  130. }
  131. function createInputHandler(converter) {
  132. /**
  133. * 创建输入处理函数,用于将输入文本转换为繁体中文
  134. * @param {Object} converter - 文本转换器对象
  135. * @returns {Function} - 处理输入事件的函数
  136. */
  137. return function handleInput(e) {
  138. try {
  139. // 检查是否需要进行转换
  140. if (!e?.target || !STATE.inputTraditional) return;
  141.  
  142. // 如果输入框标记了不需要转换,则直接返回
  143. if (e.target.dataset?.noConvert === 'true') return;
  144.  
  145. // 检查输入值是否存在
  146. const text = e.target.value;
  147. if (!text) return;
  148. // 保存当前光标位置
  149. const cursorPosition = e.target.selectionStart;
  150. // 使用requestAnimationFrame优化性能
  151. requestAnimationFrame(() => {
  152. try {
  153. // 转换文本
  154. const convertedText = converter.toTraditional(text);
  155.  
  156. // 如果转换前后文本相同,则不需要更新
  157. if (text === convertedText) return;
  158. // 更新输入框的值
  159. e.target.value = convertedText;
  160. // 恢复光标位置
  161. if (typeof cursorPosition === 'number') {
  162. e.target.setSelectionRange(cursorPosition, cursorPosition);
  163. }
  164. // 触发输入事件,确保其他监听器能够接收到更新
  165. e.target.dispatchEvent(new Event('input', {
  166. bubbles: true,
  167. cancelable: true
  168. }));
  169. } catch (error) {
  170. console.debug('转换输入文本时出错:', error);
  171. }
  172. });
  173. } catch (error) {
  174. console.debug('处理输入事件时出错:', error);
  175. }
  176. };
  177. }
  178. function convertPageText(converter, forceRestore = false) {
  179. /**
  180. * 转换页面上的文本内容(简繁转换)
  181. * @param {Object} converter - 文本转换器对象
  182. * @param {boolean} forceRestore - 是否强制恢复原始文本
  183. */
  184. // 如果不需要简体化且不是强制恢复,则直接返回
  185. if (!STATE.pageSimplified && !forceRestore) return;
  186. try {
  187. // 查找需要处理的元素
  188. const elements = document.querySelectorAll(CONFIG.textSelector);
  189. if (!elements.length) return;
  190. // 处理每个根元素
  191. elements.forEach(root => {
  192. try {
  193. // 创建TreeWalker遍历文本节点
  194. const walker = document.createTreeWalker(
  195. root,
  196. NodeFilter.SHOW_TEXT,
  197. {
  198. acceptNode: function(node) {
  199. // 过滤空文本节点
  200. if (!node.textContent.trim()) return NodeFilter.FILTER_REJECT;
  201. // 检查父节点
  202. const parent = node.parentNode;
  203. if (!parent) return NodeFilter.FILTER_REJECT;
  204. // 排除特定元素内的文本
  205. if (parent.closest?.(CONFIG.excludeSelector)) {
  206. return NodeFilter.FILTER_REJECT;
  207. }
  208. return NodeFilter.FILTER_ACCEPT;
  209. }
  210. }
  211. );
  212. // 遍历并处理每个文本节点
  213. let node;
  214. while (node = walker.nextNode()) {
  215. const text = node.textContent.trim();
  216. if (!text) continue;
  217. // 保存原始文本(如果尚未保存)
  218. if (!STATE.originalTexts.has(node)) {
  219. STATE.originalTexts.set(node, text);
  220. }
  221. // 根据当前状态进行转换或恢复
  222. if (STATE.pageSimplified) {
  223. const convertedText = converter.toSimplified(text);
  224. if (text !== convertedText) {
  225. node.textContent = convertedText;
  226. }
  227. } else {
  228. const originalText = STATE.originalTexts.get(node);
  229. if (originalText && node.textContent !== originalText) {
  230. node.textContent = originalText;
  231. }
  232. }
  233. }
  234. } catch (error) {
  235. console.debug('处理文本节点时出错:', error);
  236. }
  237. });
  238. } catch (error) {
  239. console.debug('转换页面文本时出错:', error);
  240. }
  241. }
  242. function attachInputListener(handleInput) {
  243. /**
  244. * 为页面上的输入元素添加转换事件监听器
  245. * @param {Function} handleInput - 输入处理函数
  246. */
  247. try {
  248. const inputElements = document.querySelectorAll(CONFIG.inputSelector);
  249. if (!inputElements.length) return;
  250. inputElements.forEach(element => {
  251. try {
  252. // 排除已处理的元素和搜索框
  253. if (element?.dataset?.hasConverter || element?.dataset?.isSearchInput) {
  254. return;
  255. }
  256.  
  257. // 添加输入事件监听器
  258. element.addEventListener('input', handleInput);
  259.  
  260. // 标记元素已添加转换器
  261. element.dataset.hasConverter = 'true';
  262. } catch (error) {
  263. console.debug('为输入元素添加事件监听器时出错:', error);
  264. }
  265. });
  266. } catch (error) {
  267. console.debug('查找输入元素时出错:', error);
  268. }
  269. }
  270. function createObserver(handleInput, converter) {
  271. /**
  272. * 创建DOM变化观察器,监听页面变化并处理新添加的元素
  273. * @param {Function} handleInput - 输入处理函数
  274. * @param {Object} converter - 文本转换器对象
  275. * @returns {MutationObserver} - 配置好的观察器实例
  276. */
  277. return new MutationObserver(mutations => {
  278. let needsTextConversion = false;
  279. for (const mutation of mutations) {
  280. // 跳过没有新增节点的变化
  281. if (!mutation.addedNodes.length) continue;
  282. // 检查是否有新的输入元素
  283. const hasNewInputs = Array.from(mutation.addedNodes)
  284. .some(node => {
  285. try {
  286. // 检查节点是否包含输入元素
  287. return node.nodeType === Node.ELEMENT_NODE &&
  288. node.querySelectorAll?.(CONFIG.inputSelector)?.length > 0;
  289. } catch (error) {
  290. console.debug('检查新节点时出错:', error);
  291. return false;
  292. }
  293. });
  294. // 如果有新的输入元素,为它们添加事件监听器
  295. if (hasNewInputs) {
  296. attachInputListener(handleInput);
  297. }
  298. // 标记需要进行文本转换
  299. needsTextConversion = true;
  300. }
  301. // 如果需要文本转换,延迟执行以确保DOM已完全更新
  302. if (needsTextConversion) {
  303. setTimeout(() => convertPageText(converter), 100);
  304. }
  305. });
  306. }
  307. function createConfigModal() {
  308. const modalHtml = `
  309. <div id="config-modal" style="display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
  310. background: #1a1a1a; padding: 20px; border-radius: 8px; z-index: 10000; min-width: 600px; color: #fff;">
  311. <div style="display: flex; justify-content: space-between; margin-bottom: 15px;">
  312. <h3 style="margin: 0;">ST工具箱</h3>
  313. <button id="close-config-modal" style="background: none; border: none; color: #fff; cursor: pointer;">✕</button>
  314. </div>
  315. <!-- 标签栏 -->
  316. <div style="display: flex; gap: 10px; margin-bottom: 15px; border-bottom: 1px solid #444; padding-bottom: 10px;">
  317. <button id="tab-configs" class="modal-tab active" style="padding: 8px 20px; background: #4a90e2; border: none; color: #fff; cursor: pointer; border-radius: 4px; transition: all 0.2s;">配置管理</button>
  318. <button id="tab-presets" class="modal-tab" style="padding: 8px 20px; background: #3d3d3d; border: none; color: #fff; cursor: pointer; border-radius: 4px; transition: all 0.2s;">预设关键词</button>
  319. <button id="tab-settings" class="modal-tab" style="padding: 8px 20px; background: #3d3d3d; border: none; color: #fff; cursor: pointer; border-radius: 4px; transition: all 0.2s;">设置</button>
  320. <button id="tab-contact" class="modal-tab" style="padding: 8px 20px; background: #3d3d3d; border: none; color: #fff; cursor: pointer; border-radius: 4px; transition: all 0.2s;">联系我</button>
  321. </div>
  322. <!-- 配置管理面板 -->
  323. <div id="panel-configs" class="panel" style="display: block;">
  324. <div style="padding: 15px; background: #2d2d2d; border-radius: 4px;">
  325. <div style="margin-bottom: 15px;">
  326. <input type="text" id="config-name" placeholder="配置名称"
  327. style="padding: 5px; margin-right: 10px; background: #3d3d3d; border: 1px solid #444; color: #fff; width: 200px;">
  328. <div class="custom-select" style="display: inline-block; position: relative; width: 150px; margin-right: 10px;">
  329. <input type="text" id="config-category" placeholder="选择或输入分类"
  330. style="padding: 5px; background: #3d3d3d; border: 1px solid #444; color: #fff; width: 100%; cursor: pointer;">
  331. <div id="category-dropdown" style="display: none; position: absolute; top: 100%; left: 0; width: 100%;
  332. background: #3d3d3d; border: 1px solid #444; border-top: none; max-height: 200px; overflow-y: auto; z-index: 1000;">
  333. </div>
  334. </div>
  335. <button id="save-config" style="padding: 5px 10px; background: #4a90e2; border: none; color: #fff; cursor: pointer; border-radius: 3px;">
  336. 保存当前配置
  337. </button>
  338. </div>
  339. <div id="category-tabs" style="margin-bottom: 15px; border-bottom: 1px solid #444; padding-bottom: 10px;"></div>
  340. <div id="config-list" style="max-height: 300px; overflow-y: auto;">
  341. </div>
  342. </div>
  343. </div>
  344. <!-- 设置面板 -->
  345. <div id="panel-settings" class="panel" style="display: none;">
  346. <div style="padding: 15px; background: #2d2d2d; border-radius: 4px;">
  347. <!-- 功能开关 -->
  348. <div style="margin-bottom: 20px;">
  349. <div style="font-weight: bold; margin-bottom: 10px; color: #4a90e2;">功能开关</div>
  350. <div style="display: flex; gap: 10px;">
  351. <button id="toggle-page-simplified" style="flex: 1; padding: 8px 15px; background: ${STATE.pageSimplified ? '#4a90e2' : '#3d3d3d'}; border: none; color: #fff; cursor: pointer; border-radius: 3px; transition: background-color 0.2s; text-align: center;">
  352. ${STATE.pageSimplified ? '✓ 页面简体' : '✗ 页面简体'}
  353. </button>
  354. <button id="toggle-input-traditional" style="flex: 1; padding: 8px 15px; background: ${STATE.inputTraditional ? '#4a90e2' : '#3d3d3d'}; border: none; color: #fff; cursor: pointer; border-radius: 3px; transition: background-color 0.2s; text-align: center;">
  355. ${STATE.inputTraditional ? '✓ 输入繁体' : '✗ 输入繁体'}
  356. </button>
  357. <button id="toggle-auto-load" style="flex: 1; padding: 8px 15px; background: ${STATE.autoLoadEnabled ? '#4a90e2' : '#3d3d3d'}; border: none; color: #fff; cursor: pointer; border-radius: 3px; transition: background-color 0.2s; text-align: center;">
  358. ${STATE.autoLoadEnabled ? '✓ 自动加载' : '✗ 自动加载'}
  359. </button>
  360. </div>
  361. </div>
  362. <!-- 配置导入导出 -->
  363. <div style="margin-top: 20px;">
  364. <div style="font-weight: bold; margin-bottom: 10px; color: #4a90e2;">配置导入导出</div>
  365. <div style="display: flex; gap: 10px;">
  366. <button id="export-configs" style="flex: 1; padding: 8px 15px; background: #27ae60; border: none; color: #fff; cursor: pointer; border-radius: 3px;">导出配置</button>
  367. <button id="import-configs" style="flex: 1; padding: 8px 15px; background: #e67e22; border: none; color: #fff; cursor: pointer; border-radius: 3px;">导入配置</button>
  368. <input type="file" id="import-file" accept=".json" style="display: none;">
  369. </div>
  370. </div>
  371. </div>
  372. </div>
  373.  
  374. <!-- 预设关键词面板 -->
  375. <div id="panel-presets" class="panel" style="display: none;">
  376. <div style="padding: 15px; background: #2d2d2d; border-radius: 4px;">
  377. <div style="margin-bottom: 15px; display: flex; justify-content: flex-end;">
  378. <button id="add-preset" style="padding: 8px 20px; background: #4a90e2; border: none; color: #fff; cursor: pointer; border-radius: 4px; transition: all 0.2s;">
  379. 添加预设
  380. </button>
  381. </div>
  382. <div id="preset-list" style="max-height: 400px; overflow-y: auto;">
  383. </div>
  384. </div>
  385. </div>
  386. <!-- 联系我面板 -->
  387. <div id="panel-contact" class="panel" style="display: none;">
  388. <div style="padding: 15px; background: #2d2d2d; border-radius: 4px;">
  389. <div style="text-align: center; padding: 20px;">
  390. <div style="font-size: 18px; color: #4a90e2; margin-bottom: 15px;">欢迎加入POE2 ST工具箱交流群</div>
  391. <div style="font-size: 24px; color: #FFD700; margin-bottom: 15px;">QQ群:858024457</div>
  392. <div style="color: #999; font-size: 14px;">
  393. 欢迎各位流亡者加入交流群,反馈使用问题,提出建议!
  394. </div>
  395. </div>
  396. </div>
  397. </div>
  398. </div>
  399.  
  400. <!-- 预设编辑弹窗 -->
  401. <div id="preset-edit-modal" style="display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
  402. background: #1a1a1a; padding: 20px; border-radius: 8px; z-index: 10002; width: 800px; color: #fff; box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);">
  403. <div style="display: flex; justify-content: space-between; margin-bottom: 15px;">
  404. <h3 style="margin: 0;" id="preset-edit-title">添加预设</h3>
  405. <button id="close-preset-edit" style="background: none; border: none; color: #fff; cursor: pointer; font-size: 20px;">✕</button>
  406. </div>
  407. <div style="margin-bottom: 15px;">
  408. <div style="margin-bottom: 10px;">
  409. <label style="display: block; margin-bottom: 5px;">预设名称</label>
  410. <input type="text" id="preset-name" style="width: 100%; padding: 8px; background: #2d2d2d; border: 1px solid #444; color: #fff; border-radius: 4px;">
  411. </div>
  412. <div>
  413. <label style="display: block; margin-bottom: 5px;">关键词(用;分隔)</label>
  414. <textarea id="preset-keywords" style="width: 100%; height: 200px; padding: 8px; background: #2d2d2d; border: 1px solid #444; color: #fff; border-radius: 4px; resize: vertical; font-family: monospace;"></textarea>
  415. </div>
  416. </div>
  417. <div style="display: flex; justify-content: flex-end; gap: 10px;">
  418. <button id="cancel-preset-edit" style="padding: 8px 20px; background: #3d3d3d; border: none; color: #fff; cursor: pointer; border-radius: 4px;">
  419. 取消
  420. </button>
  421. <button id="save-preset" style="padding: 8px 20px; background: #4a90e2; border: none; color: #fff; cursor: pointer; border-radius: 4px;">
  422. 保存
  423. </button>
  424. </div>
  425. </div>
  426.  
  427. <!-- 添加遮罩层 -->
  428. <div id="preset-edit-overlay" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.7); z-index: 10001;"></div>
  429. </div>
  430. `;
  431. document.body.insertAdjacentHTML('beforeend', modalHtml);
  432. // 添加遮罩
  433. const overlay = document.createElement('div');
  434. overlay.id = 'config-modal-overlay';
  435. overlay.style.cssText = `
  436. display: none;
  437. position: fixed;
  438. top: 0;
  439. left: 0;
  440. width: 100%;
  441. height: 100%;
  442. background: rgba(0, 0, 0, 0.5);
  443. z-index: 9999;
  444. `;
  445. document.body.appendChild(overlay);
  446. // 添加样式
  447. const style = document.createElement('style');
  448. style.textContent = `
  449. .modal-tab.active {
  450. background: #4a90e2 !important;
  451. }
  452. .modal-tab:hover {
  453. background: #357abd !important;
  454. }
  455. .panel {
  456. transition: opacity 0.3s ease;
  457. }
  458. #config-list::-webkit-scrollbar {
  459. width: 8px;
  460. }
  461. #config-list::-webkit-scrollbar-track {
  462. background: #1a1a1a;
  463. }
  464. #config-list::-webkit-scrollbar-thumb {
  465. background: #444;
  466. border-radius: 4px;
  467. }
  468. #config-list::-webkit-scrollbar-thumb:hover {
  469. background: #555;
  470. }
  471. `;
  472. document.head.appendChild(style);
  473. setupConfigModalEvents();
  474. updateConfigList();
  475. setupCategoryDropdown();
  476. }
  477. function setupCategoryDropdown() {
  478. const categoryInput = document.getElementById('config-category');
  479. const dropdown = document.getElementById('category-dropdown');
  480. let isDropdownVisible = false;
  481. function updateDropdown() {
  482. const categories = Object.keys(STATE.configs);
  483. const inputValue = categoryInput.value.toLowerCase();
  484. dropdown.innerHTML = '';
  485. categories
  486. .filter(category => category.toLowerCase().includes(inputValue))
  487. .forEach(category => {
  488. const item = document.createElement('div');
  489. item.className = 'dropdown-item';
  490. item.textContent = category;
  491. item.onclick = () => {
  492. categoryInput.value = category;
  493. hideDropdown();
  494. };
  495. dropdown.appendChild(item);
  496. });
  497. if (categories.length === 0) {
  498. const item = document.createElement('div');
  499. item.className = 'dropdown-item';
  500. item.textContent = '无已有分类';
  501. item.style.color = '#666';
  502. dropdown.appendChild(item);
  503. }
  504. }
  505. function showDropdown() {
  506. updateDropdown();
  507. dropdown.style.display = 'block';
  508. isDropdownVisible = true;
  509. }
  510. function hideDropdown() {
  511. dropdown.style.display = 'none';
  512. isDropdownVisible = false;
  513. }
  514. categoryInput.addEventListener('focus', showDropdown);
  515. categoryInput.addEventListener('input', updateDropdown);
  516. // 点击外部区域时隐藏下拉列表
  517. document.addEventListener('click', (e) => {
  518. const isClickInside = categoryInput.contains(e.target) || dropdown.contains(e.target);
  519. if (!isClickInside && isDropdownVisible) {
  520. hideDropdown();
  521. }
  522. });
  523. // 阻止事件冒泡,避免点击下拉列表时触发外部点击事件
  524. dropdown.addEventListener('click', (e) => {
  525. e.stopPropagation();
  526. });
  527. }
  528. function setupConfigModalEvents() {
  529. const modal = document.getElementById('config-modal');
  530. const overlay = document.getElementById('config-modal-overlay');
  531. const closeBtn = document.getElementById('close-config-modal');
  532. const saveBtn = document.getElementById('save-config');
  533. const togglePageBtn = document.getElementById('toggle-page-simplified');
  534. const toggleInputBtn = document.getElementById('toggle-input-traditional');
  535. const toggleAutoLoadBtn = document.getElementById('toggle-auto-load');
  536. const exportBtn = document.getElementById('export-configs');
  537. const importBtn = document.getElementById('import-configs');
  538. const importFile = document.getElementById('import-file');
  539. const tabConfigs = document.getElementById('tab-configs');
  540. const tabSettings = document.getElementById('tab-settings');
  541. const tabPresets = document.getElementById('tab-presets');
  542. const tabContact = document.getElementById('tab-contact');
  543. const panelConfigs = document.getElementById('panel-configs');
  544. const panelSettings = document.getElementById('panel-settings');
  545. const panelPresets = document.getElementById('panel-presets');
  546. const panelContact = document.getElementById('panel-contact');
  547. const savePresetBtn = document.getElementById('save-preset');
  548.  
  549. const addPresetBtn = document.getElementById('add-preset');
  550. const presetEditModal = document.getElementById('preset-edit-modal');
  551. const presetEditOverlay = document.getElementById('preset-edit-overlay');
  552. const closePresetEdit = document.getElementById('close-preset-edit');
  553. const cancelPresetEdit = document.getElementById('cancel-preset-edit');
  554. const presetEditTitle = document.getElementById('preset-edit-title');
  555.  
  556. // 标签切换函数
  557. function switchTab(activeTab) {
  558. // 重置所有标签和面板
  559. [tabConfigs, tabSettings, tabPresets, tabContact].forEach(tab => {
  560. tab.classList.remove('active');
  561. tab.style.background = '#3d3d3d';
  562. });
  563. [panelConfigs, panelSettings, panelPresets, panelContact].forEach(panel => {
  564. panel.style.display = 'none';
  565. });
  566.  
  567. // 激活选中的标签和面板
  568. activeTab.classList.add('active');
  569. activeTab.style.background = '#4a90e2';
  570. // 显示对应的面板
  571. if (activeTab === tabConfigs) {
  572. panelConfigs.style.display = 'block';
  573. } else if (activeTab === tabSettings) {
  574. panelSettings.style.display = 'block';
  575. } else if (activeTab === tabPresets) {
  576. panelPresets.style.display = 'block';
  577. updatePresetList();
  578. } else if (activeTab === tabContact) {
  579. panelContact.style.display = 'block';
  580. }
  581. }
  582.  
  583. // 标签切换事件
  584. tabConfigs.addEventListener('click', () => switchTab(tabConfigs));
  585. tabSettings.addEventListener('click', () => switchTab(tabSettings));
  586. tabPresets.addEventListener('click', () => switchTab(tabPresets));
  587. tabContact.addEventListener('click', () => switchTab(tabContact));
  588. // 初始化显示配置管理标签
  589. switchTab(tabConfigs);
  590.  
  591. closeBtn.addEventListener('click', () => {
  592. modal.style.display = 'none';
  593. overlay.style.display = 'none';
  594. });
  595. overlay.addEventListener('click', () => {
  596. modal.style.display = 'none';
  597. overlay.style.display = 'none';
  598. });
  599. togglePageBtn.addEventListener('click', () => {
  600. STATE.pageSimplified = !STATE.pageSimplified;
  601. GM_setValue('pageSimplified', STATE.pageSimplified);
  602. togglePageBtn.textContent = STATE.pageSimplified ? '✓ 页面简体' : '✗ 页面简体';
  603. togglePageBtn.style.backgroundColor = STATE.pageSimplified ? '#4a90e2' : '#3d3d3d';
  604. convertPageText(window.converter, true);
  605. });
  606. toggleInputBtn.addEventListener('click', () => {
  607. STATE.inputTraditional = !STATE.inputTraditional;
  608. GM_setValue('inputTraditional', STATE.inputTraditional);
  609. toggleInputBtn.textContent = STATE.inputTraditional ? '✓ 输入繁体' : '✗ 输入繁体';
  610. toggleInputBtn.style.backgroundColor = STATE.inputTraditional ? '#4a90e2' : '#3d3d3d';
  611. });
  612. toggleAutoLoadBtn.addEventListener('click', () => {
  613. STATE.autoLoadEnabled = !STATE.autoLoadEnabled;
  614. GM_setValue('autoLoadEnabled', STATE.autoLoadEnabled);
  615. toggleAutoLoadBtn.textContent = STATE.autoLoadEnabled ? '✓ 自动加载' : '✗ 自动加载';
  616. toggleAutoLoadBtn.style.backgroundColor = STATE.autoLoadEnabled ? '#4a90e2' : '#3d3d3d';
  617. });
  618. saveBtn.addEventListener('click', saveCurrentConfig);
  619. // 修改导出配置
  620. exportBtn.addEventListener('click', () => {
  621. const configData = {
  622. version: '2.0.0',
  623. configs: {},
  624. searchPresets: STATE.searchPresets
  625. };
  626.  
  627. // 复制配置,但不包含 timestamp
  628. Object.keys(STATE.configs).forEach(category => {
  629. configData.configs[category] = {};
  630. Object.keys(STATE.configs[category]).forEach(name => {
  631. configData.configs[category][name] = {
  632. url: STATE.configs[category][name].url
  633. };
  634. });
  635. });
  636. const blob = new Blob([JSON.stringify(configData, null, 2)], { type: 'application/json' });
  637. const url = URL.createObjectURL(blob);
  638. const a = document.createElement('a');
  639. a.href = url;
  640. a.download = `poe2_trade_configs_${new Date().toISOString().slice(0,10)}.json`;
  641. document.body.appendChild(a);
  642. a.click();
  643. document.body.removeChild(a);
  644. URL.revokeObjectURL(url);
  645. });
  646. // 导入配置按钮点击
  647. importBtn.addEventListener('click', () => {
  648. importFile.click();
  649. });
  650. // 处理文件导入
  651. importFile.addEventListener('change', (e) => {
  652. const file = e.target.files[0];
  653. if (!file) return;
  654. const reader = new FileReader();
  655. reader.onload = (event) => {
  656. try {
  657. const importedData = JSON.parse(event.target.result);
  658. // 验证导入的数据
  659. if (!importedData.version || (!importedData.configs && !importedData.searchPresets)) {
  660. throw new Error('无效的配置文件格式');
  661. }
  662. // 确认导入
  663. if (confirm(`确定要导入这些配置吗?\n这将会覆盖同名的现有配置和预设关键词。`)) {
  664. // 合并配置
  665. if (importedData.configs) {
  666. Object.keys(importedData.configs).forEach(category => {
  667. if (!STATE.configs[category]) {
  668. STATE.configs[category] = {};
  669. }
  670. Object.assign(STATE.configs[category], importedData.configs[category]);
  671. });
  672. GM_setValue('savedConfigs', STATE.configs);
  673. updateConfigList();
  674. }
  675.  
  676. // 合并预设关键词
  677. if (importedData.searchPresets) {
  678. Object.assign(STATE.searchPresets, importedData.searchPresets);
  679. GM_setValue('searchPresets', STATE.searchPresets);
  680. updatePresetList();
  681. }
  682.  
  683. alert('配置导入成功!');
  684. }
  685. } catch (error) {
  686. alert('导入失败:' + error.message);
  687. }
  688. // 清除文件选择,允许重复导入同一个文件
  689. importFile.value = '';
  690. };
  691. reader.readAsText(file);
  692. });
  693. // 添加预设标签切换事件
  694. tabPresets.addEventListener('click', () => {
  695. tabPresets.classList.add('active');
  696. tabConfigs.classList.remove('active');
  697. tabSettings.classList.remove('active');
  698. tabConfigs.style.background = '#3d3d3d';
  699. tabSettings.style.background = '#3d3d3d';
  700. panelConfigs.style.display = 'none';
  701. panelSettings.style.display = 'none';
  702. panelPresets.style.display = 'block';
  703. updatePresetList();
  704. });
  705. // 添加保存预设事件
  706. savePresetBtn.addEventListener('click', saveSearchPreset);
  707.  
  708. function closePresetEditModal() {
  709. presetEditModal.style.display = 'none';
  710. presetEditOverlay.style.display = 'none';
  711. }
  712.  
  713. // 打开预设编辑弹窗
  714. addPresetBtn.addEventListener('click', () => {
  715. presetEditModal.style.display = 'block';
  716. presetEditOverlay.style.display = 'block';
  717. presetEditTitle.textContent = '添加预设';
  718. document.getElementById('preset-name').value = '';
  719. document.getElementById('preset-keywords').value = '';
  720. document.getElementById('preset-name').dataset.editMode = 'false';
  721. });
  722.  
  723. // 关闭预设编辑弹窗
  724. [closePresetEdit, cancelPresetEdit, presetEditOverlay].forEach(btn => {
  725. btn.addEventListener('click', closePresetEditModal);
  726. });
  727.  
  728. // 添加预设名称和关键词输入框的事件处理
  729. const presetNameInput = document.getElementById('preset-name');
  730. const presetKeywordsInput = document.getElementById('preset-keywords');
  731.  
  732. // 标记这些输入框不需要转换
  733. presetNameInput.dataset.noConvert = 'true';
  734. presetKeywordsInput.dataset.noConvert = 'true';
  735.  
  736. // 移除原有的输入转换处理
  737. [presetNameInput, presetKeywordsInput].forEach(input => {
  738. const oldInput = input.cloneNode(true);
  739. input.parentNode.replaceChild(oldInput, input);
  740. });
  741. }
  742. // 修改 saveCurrentConfig 函数
  743. function saveCurrentConfig() {
  744. const name = document.getElementById('config-name').value.trim();
  745. const category = document.getElementById('config-category').value.trim();
  746.  
  747. if (!name) {
  748. alert('请输入配置名称');
  749. return;
  750. }
  751.  
  752. if (!category) {
  753. alert('请输入分类名称');
  754. return;
  755. }
  756.  
  757. if (!STATE.configs[category]) {
  758. STATE.configs[category] = {};
  759. }
  760.  
  761. STATE.configs[category][name] = {
  762. url: window.location.href
  763. };
  764.  
  765. GM_setValue('savedConfigs', STATE.configs);
  766. updateConfigList();
  767.  
  768. document.getElementById('config-name').value = '';
  769. document.getElementById('config-category').value = '';
  770. }
  771. function updateConfigList() {
  772. const configList = document.getElementById('config-list');
  773. const categoryTabs = document.getElementById('category-tabs');
  774. configList.innerHTML = '';
  775. categoryTabs.innerHTML = '';
  776. // 获取所有分类
  777. const categories = Object.keys(STATE.configs);
  778. // 如果没有配置,显示提示信息
  779. if (categories.length === 0) {
  780. configList.innerHTML = '<div style="text-align: center; color: #666;">暂无保存的配置</div>';
  781. return;
  782. }
  783. // 创建标签
  784. categories.forEach((category, index) => {
  785. const tabButton = document.createElement('button');
  786. tabButton.textContent = category;
  787. tabButton.style.cssText = `
  788. background: ${index === 0 ? '#4a90e2' : '#3d3d3d'};
  789. border: none;
  790. color: #fff;
  791. padding: 5px 15px;
  792. cursor: pointer;
  793. border-radius: 3px;
  794. transition: background-color 0.2s;
  795. margin-right: 10px;
  796. `;
  797. tabButton.dataset.category = category;
  798. tabButton.title = '双击删除分类';
  799. tabButton.addEventListener('click', (e) => {
  800. document.querySelectorAll('#category-tabs button[data-category]').forEach(btn => {
  801. btn.style.backgroundColor = '#3d3d3d';
  802. });
  803. tabButton.style.backgroundColor = '#4a90e2';
  804. showCategoryConfigs(category);
  805. });
  806. tabButton.addEventListener('dblclick', (e) => {
  807. e.stopPropagation();
  808. deleteCategory(category);
  809. });
  810. categoryTabs.appendChild(tabButton);
  811. });
  812. // 默认显示第一个分类的配置
  813. showCategoryConfigs(categories[0]);
  814. }
  815. function deleteCategory(category) {
  816. /**
  817. * 删除指定的配置类别及其所有配置
  818. * @param {string} category - 要删除的配置类别
  819. */
  820. try {
  821. // 检查类别是否存在
  822. if (!STATE.configs[category]) {
  823. console.warn(`类别不存在: ${category}`);
  824. return;
  825. }
  826.  
  827. // 获取该类别下的配置数量
  828. const configCount = Object.keys(STATE.configs[category]).length;
  829.  
  830. // 确认删除操作
  831. if (confirm(`确定要删除分类 "${category}" 及其包含的 ${configCount} 个配置吗?`)) {
  832. // 删除类别
  833. delete STATE.configs[category];
  834.  
  835. // 保存更新后的配置
  836. GM_setValue('savedConfigs', STATE.configs);
  837.  
  838. // 更新配置列表显示
  839. updateConfigList();
  840.  
  841. console.log(`已删除类别: ${category} (包含 ${configCount} 个配置)`);
  842. }
  843. } catch (error) {
  844. console.error('删除类别时出错:', error);
  845. }
  846. }
  847. function showCategoryConfigs(category) {
  848. /**
  849. * 显示指定类别的所有配置
  850. * @param {string} category - 配置类别
  851. */
  852. try {
  853. // 获取配置列表容器
  854. const configList = document.getElementById('config-list');
  855. if (!configList) {
  856. console.warn('无法找到配置列表容器');
  857. return;
  858. }
  859.  
  860. // 清空当前列表
  861. configList.innerHTML = '';
  862.  
  863. // 获取指定类别的配置
  864. const configs = STATE.configs[category];
  865. if (!configs || Object.keys(configs).length === 0) {
  866. const emptyMessage = document.createElement('div');
  867. emptyMessage.textContent = '该分类下没有配置';
  868. emptyMessage.style.padding = '10px';
  869. emptyMessage.style.color = '#999';
  870. configList.appendChild(emptyMessage);
  871. return;
  872. }
  873.  
  874. // 遍历并显示每个配置
  875. Object.entries(configs).forEach(([name, data]) => {
  876. // 创建配置项容器
  877. const configItem = document.createElement('div');
  878. configItem.style.cssText = `
  879. display: grid;
  880. grid-template-columns: 1fr auto auto auto;
  881. align-items: center;
  882. padding: 8px;
  883. margin: 5px 0;
  884. background: #3d3d3d;
  885. border-radius: 4px;
  886. gap: 10px;
  887. `;
  888. // 创建配置名称元素
  889. const nameSpan = document.createElement('span');
  890. nameSpan.textContent = name;
  891. nameSpan.style.cssText = `
  892. overflow: hidden;
  893. text-overflow: ellipsis;
  894. white-space: nowrap;
  895. `;
  896. // 创建读取按钮
  897. const loadBtn = document.createElement('button');
  898. loadBtn.textContent = '读取';
  899. loadBtn.style.cssText = `
  900. background: #4a90e2;
  901. border: none;
  902. color: #fff;
  903. padding: 3px 12px;
  904. cursor: pointer;
  905. border-radius: 3px;
  906. transition: background-color 0.2s;
  907. `;
  908. loadBtn.onclick = () => {
  909. loadConfig(data.url);
  910. };
  911. // 创建更新按钮
  912. const updateBtn = document.createElement('button');
  913. updateBtn.textContent = '更新';
  914. updateBtn.style.cssText = `
  915. background: #27ae60;
  916. border: none;
  917. color: #fff;
  918. padding: 3px 12px;
  919. cursor: pointer;
  920. border-radius: 3px;
  921. transition: background-color 0.2s;
  922. `;
  923. updateBtn.onclick = (e) => {
  924. e.stopPropagation();
  925. updateConfig(category, name);
  926. };
  927. // 创建删除按钮
  928. const deleteBtn = document.createElement('button');
  929. deleteBtn.textContent = '删除';
  930. deleteBtn.style.cssText = `
  931. background: #e74c3c;
  932. border: none;
  933. color: #fff;
  934. padding: 3px 12px;
  935. cursor: pointer;
  936. border-radius: 3px;
  937. transition: background-color 0.2s;
  938. `;
  939. deleteBtn.onclick = (e) => {
  940. e.stopPropagation();
  941. deleteConfig(category, name);
  942. };
  943. // 添加所有元素到配置项
  944. configItem.appendChild(nameSpan);
  945. configItem.appendChild(loadBtn);
  946. configItem.appendChild(updateBtn);
  947. configItem.appendChild(deleteBtn);
  948.  
  949. // 添加配置项到列表
  950. configList.appendChild(configItem);
  951. });
  952. } catch (error) {
  953. console.error('显示配置列表时出错:', error);
  954. }
  955. }
  956. function loadConfig(url) {
  957. /**
  958. * 加载指定URL的配置
  959. * @param {string} url - 要加载的配置URL
  960. */
  961. try {
  962. if (!url) {
  963. console.warn('加载配置失败: URL为空');
  964. return;
  965. }
  966.  
  967. // 导航到指定URL
  968. window.location.href = url;
  969. console.log('正在加载配置:', url);
  970. } catch (error) {
  971. console.error('加载配置时出错:', error);
  972. }
  973. }
  974. function deleteConfig(category, name) {
  975. /**
  976. * 删除指定类别和名称的配置
  977. * @param {string} category - 配置类别
  978. * @param {string} name - 配置名称
  979. */
  980. try {
  981. // 确认删除操作
  982. if (confirm(`确定要删除配置 "${name}" 吗?`)) {
  983. // 删除指定配置
  984. if (STATE.configs[category] && STATE.configs[category][name]) {
  985. delete STATE.configs[category][name];
  986.  
  987. // 如果类别下没有配置了,删除整个类别
  988. if (Object.keys(STATE.configs[category]).length === 0) {
  989. delete STATE.configs[category];
  990. }
  991.  
  992. // 保存更新后的配置
  993. GM_setValue('savedConfigs', STATE.configs);
  994.  
  995. // 更新配置列表显示
  996. updateConfigList();
  997.  
  998. console.log(`已删除配置: ${category}/${name}`);
  999. } else {
  1000. console.warn(`配置不存在: ${category}/${name}`);
  1001. }
  1002. }
  1003. } catch (error) {
  1004. console.error('删除配置时出错:', error);
  1005. }
  1006. }
  1007. function createConfigButton() {
  1008. const floatingButton = document.createElement('div');
  1009. floatingButton.style.cssText = `
  1010. position: fixed;
  1011. right: 20px;
  1012. top: 50%;
  1013. transform: translateY(-50%);
  1014. width: 50px;
  1015. height: 50px;
  1016. background: linear-gradient(135deg, #3c3c28 0%, #2a2a1c 100%);
  1017. border-radius: 25px;
  1018. display: flex;
  1019. align-items: center;
  1020. justify-content: center;
  1021. cursor: pointer;
  1022. color: #ffd700;
  1023. font-weight: bold;
  1024. font-family: 'Fontin SmallCaps', Arial, sans-serif;
  1025. font-size: 18px;
  1026. box-shadow: 0 0 20px rgba(0,0,0,0.5),
  1027. inset 0 0 8px rgba(255, 215, 0, 0.3),
  1028. 0 0 30px rgba(255, 215, 0, 0.2);
  1029. border: 1px solid rgba(255, 215, 0, 0.4);
  1030. z-index: 9998;
  1031. transition: all 0.3s ease;
  1032. user-select: none;
  1033. touch-action: none;
  1034. text-shadow: 0 0 10px rgba(255, 215, 0, 0.6);
  1035. animation: normalGlowing 2s ease-in-out infinite;
  1036. `;
  1037. floatingButton.textContent = 'ST';
  1038. floatingButton.title = 'ST工具箱 (按住可拖动)';
  1039. // 添加悬停效果
  1040. floatingButton.addEventListener('mouseenter', () => {
  1041. if (!isDragging) {
  1042. floatingButton.style.transform = 'scale(1.1)';
  1043. floatingButton.style.boxShadow = `
  1044. 0 0 25px rgba(0,0,0,0.5),
  1045. inset 0 0 12px rgba(255, 215, 0, 0.5),
  1046. 0 0 40px rgba(255, 215, 0, 0.3)
  1047. `;
  1048. floatingButton.style.color = '#ffe44d';
  1049. floatingButton.style.textShadow = '0 0 15px rgba(255, 215, 0, 0.8)';
  1050. floatingButton.style.border = '1px solid rgba(255, 215, 0, 0.6)';
  1051. floatingButton.style.animation = 'none';
  1052. if (isHidden) {
  1053. showButton();
  1054. }
  1055. }
  1056. });
  1057. floatingButton.addEventListener('mouseleave', () => {
  1058. if (!isDragging) {
  1059. floatingButton.style.transform = 'scale(1)';
  1060. floatingButton.style.boxShadow = `
  1061. 0 0 20px rgba(0,0,0,0.5),
  1062. inset 0 0 8px rgba(255, 215, 0, 0.3),
  1063. 0 0 30px rgba(255, 215, 0, 0.2)
  1064. `;
  1065. floatingButton.style.color = '#ffd700';
  1066. floatingButton.style.textShadow = '0 0 10px rgba(255, 215, 0, 0.6)';
  1067. floatingButton.style.border = '1px solid rgba(255, 215, 0, 0.4)';
  1068. floatingButton.style.animation = 'normalGlowing 2s ease-in-out infinite';
  1069. checkAndHideButton();
  1070. }
  1071. });
  1072. // 添加拖拽功能
  1073. let isDragging = false;
  1074. let startX, startY;
  1075. let lastX = GM_getValue('floatingButtonX', window.innerWidth - 70);
  1076. let lastY = GM_getValue('floatingButtonY', window.innerHeight / 2);
  1077. let dragDistance = 0;
  1078. let mouseDownTime = 0;
  1079. let isHidden = false;
  1080. function dragStart(e) {
  1081. isDragging = true;
  1082. dragDistance = 0;
  1083. mouseDownTime = Date.now();
  1084. const rect = floatingButton.getBoundingClientRect();
  1085. startX = e.clientX - rect.left;
  1086. startY = e.clientY - rect.top;
  1087. floatingButton.style.transition = 'none';
  1088. floatingButton.style.transform = 'scale(1)';
  1089. }
  1090. function drag(e) {
  1091. if (!isDragging) return;
  1092. e.preventDefault();
  1093. const x = e.clientX - startX;
  1094. const y = e.clientY - startY;
  1095. // 计算拖动距离
  1096. const dx = x - lastX;
  1097. const dy = y - lastY;
  1098. dragDistance += Math.sqrt(dx * dx + dy * dy);
  1099. // 限制拖动范围,添加边距防止完全贴边
  1100. const margin = 20; // 边距
  1101. const maxX = window.innerWidth - floatingButton.offsetWidth - margin;
  1102. const maxY = window.innerHeight - floatingButton.offsetHeight - margin;
  1103. const minX = margin;
  1104. const minY = margin;
  1105. lastX = Math.max(minX, Math.min(x, maxX));
  1106. lastY = Math.max(minY, Math.min(y, maxY));
  1107. floatingButton.style.left = lastX + 'px';
  1108. floatingButton.style.top = lastY + 'px';
  1109. floatingButton.style.right = 'auto';
  1110. }
  1111. function dragEnd(e) {
  1112. if (!isDragging) return;
  1113. const dragDuration = Date.now() - mouseDownTime;
  1114. isDragging = false;
  1115. floatingButton.style.transition = 'all 0.3s ease';
  1116. // 保存位置
  1117. GM_setValue('floatingButtonX', lastX);
  1118. GM_setValue('floatingButtonY', lastY);
  1119. // 如果拖动距离小于5像素且时间小于200ms,则认为是点击
  1120. if (dragDistance < 5 && dragDuration < 200) {
  1121. document.getElementById('config-modal').style.display = 'block';
  1122. document.getElementById('config-modal-overlay').style.display = 'block';
  1123. }
  1124. }
  1125. function checkAndHideButton() {
  1126. const threshold = 20; // 距离边缘多少像素时触发隐藏
  1127. const buttonWidth = floatingButton.offsetWidth;
  1128. const buttonHeight = floatingButton.offsetHeight;
  1129. const buttonRight = lastX + buttonWidth;
  1130. const buttonBottom = lastY + buttonHeight;
  1131. const windowWidth = window.innerWidth;
  1132. const windowHeight = window.innerHeight;
  1133. // 检查各个边缘
  1134. if (buttonRight > windowWidth - threshold) {
  1135. // 右边缘
  1136. hideButton('right');
  1137. } else if (lastX < threshold) {
  1138. // 左边缘
  1139. hideButton('left');
  1140. } else if (lastY < threshold) {
  1141. // 上边缘
  1142. hideButton('top');
  1143. } else if (buttonBottom > windowHeight - threshold) {
  1144. // 下边缘
  1145. hideButton('bottom');
  1146. }
  1147. }
  1148. function hideButton(direction) {
  1149. isHidden = true;
  1150. floatingButton.style.transition = 'all 0.3s ease';
  1151. // 添加金光动画
  1152. floatingButton.style.animation = 'none';
  1153. floatingButton.offsetHeight; // 触发重绘
  1154. floatingButton.style.animation = 'glowing 1.5s ease-in-out infinite';
  1155. floatingButton.style.background = 'linear-gradient(135deg, #5a5a42 0%, #3a3a2c 100%)';
  1156. switch (direction) {
  1157. case 'right':
  1158. floatingButton.style.transform = 'translateY(-50%) translateX(60%)';
  1159. floatingButton.style.borderRadius = '25px 0 0 25px';
  1160. break;
  1161. case 'left':
  1162. floatingButton.style.transform = 'translateY(-50%) translateX(-60%)';
  1163. floatingButton.style.borderRadius = '0 25px 25px 0';
  1164. break;
  1165. case 'top':
  1166. floatingButton.style.transform = 'translateX(-50%) translateY(-60%)';
  1167. floatingButton.style.borderRadius = '0 0 25px 25px';
  1168. break;
  1169. case 'bottom':
  1170. floatingButton.style.transform = 'translateX(-50%) translateY(60%)';
  1171. floatingButton.style.borderRadius = '25px 25px 0 0';
  1172. break;
  1173. }
  1174. }
  1175. function showButton() {
  1176. isHidden = false;
  1177. floatingButton.style.transition = 'all 0.3s ease';
  1178. floatingButton.style.animation = 'normalGlowing 2s ease-in-out infinite';
  1179. floatingButton.style.background = 'linear-gradient(135deg, #3c3c28 0%, #2a2a1c 100%)';
  1180. floatingButton.style.transform = 'scale(1)';
  1181. floatingButton.style.borderRadius = '25px';
  1182. }
  1183. // 添加金光动画样式
  1184. const glowingStyle = document.createElement('style');
  1185. glowingStyle.textContent = `
  1186. @keyframes normalGlowing {
  1187. 0% {
  1188. box-shadow: 0 0 20px rgba(0,0,0,0.5),
  1189. inset 0 0 8px rgba(255, 215, 0, 0.3),
  1190. 0 0 30px rgba(255, 215, 0, 0.2);
  1191. border-color: rgba(255, 215, 0, 0.4);
  1192. color: #ffd700;
  1193. text-shadow: 0 0 10px rgba(255, 215, 0, 0.6);
  1194. }
  1195. 50% {
  1196. box-shadow: 0 0 25px rgba(0,0,0,0.5),
  1197. inset 0 0 12px rgba(255, 215, 0, 0.4),
  1198. 0 0 40px rgba(255, 215, 0, 0.3),
  1199. 0 0 60px rgba(255, 215, 0, 0.2);
  1200. border-color: rgba(255, 215, 0, 0.5);
  1201. color: #ffe44d;
  1202. text-shadow: 0 0 15px rgba(255, 215, 0, 0.7);
  1203. }
  1204. 100% {
  1205. box-shadow: 0 0 20px rgba(0,0,0,0.5),
  1206. inset 0 0 8px rgba(255, 215, 0, 0.3),
  1207. 0 0 30px rgba(255, 215, 0, 0.2);
  1208. border-color: rgba(255, 215, 0, 0.4);
  1209. color: #ffd700;
  1210. text-shadow: 0 0 10px rgba(255, 215, 0, 0.6);
  1211. }
  1212. }
  1213. @keyframes glowing {
  1214. 0% {
  1215. box-shadow: 0 0 20px rgba(0,0,0,0.5),
  1216. inset 0 0 8px rgba(255, 215, 0, 0.5),
  1217. 0 0 30px rgba(255, 215, 0, 0.4),
  1218. 0 0 50px rgba(255, 215, 0, 0.2);
  1219. border-color: rgba(255, 215, 0, 0.6);
  1220. color: #ffd700;
  1221. text-shadow: 0 0 15px rgba(255, 215, 0, 0.8);
  1222. }
  1223. 50% {
  1224. box-shadow: 0 0 30px rgba(0,0,0,0.6),
  1225. inset 0 0 20px rgba(255, 215, 0, 0.8),
  1226. 0 0 60px rgba(255, 215, 0, 0.6),
  1227. 0 0 100px rgba(255, 215, 0, 0.4),
  1228. 0 0 150px rgba(255, 215, 0, 0.2);
  1229. border-color: rgba(255, 223, 0, 1);
  1230. color: #ffe44d;
  1231. text-shadow: 0 0 25px rgba(255, 215, 0, 1),
  1232. 0 0 35px rgba(255, 215, 0, 0.7),
  1233. 0 0 45px rgba(255, 215, 0, 0.4);
  1234. }
  1235. 100% {
  1236. box-shadow: 0 0 20px rgba(0,0,0,0.5),
  1237. inset 0 0 8px rgba(255, 215, 0, 0.5),
  1238. 0 0 30px rgba(255, 215, 0, 0.4),
  1239. 0 0 50px rgba(255, 215, 0, 0.2);
  1240. border-color: rgba(255, 215, 0, 0.6);
  1241. color: #ffd700;
  1242. text-shadow: 0 0 15px rgba(255, 215, 0, 0.8);
  1243. }
  1244. }
  1245. `;
  1246. document.head.appendChild(glowingStyle);
  1247. // 监听窗口大小变化
  1248. window.addEventListener('resize', () => {
  1249. if (!isDragging) {
  1250. const margin = 20;
  1251. const maxX = window.innerWidth - floatingButton.offsetWidth - margin;
  1252. const maxY = window.innerHeight - floatingButton.offsetHeight - margin;
  1253. const minX = margin;
  1254. const minY = margin;
  1255. lastX = Math.max(minX, Math.min(lastX, maxX));
  1256. lastY = Math.max(minY, Math.min(lastY, maxY));
  1257. floatingButton.style.left = lastX + 'px';
  1258. floatingButton.style.top = lastY + 'px';
  1259. }
  1260. });
  1261. floatingButton.addEventListener('mousedown', dragStart);
  1262. document.addEventListener('mousemove', drag);
  1263. document.addEventListener('mouseup', dragEnd);
  1264. // 恢复上次保存的位置或使用默认位置
  1265. const margin = 20;
  1266. const maxX = window.innerWidth - 70 - margin;
  1267. const maxY = window.innerHeight / 2;
  1268. const minX = margin;
  1269. const minY = margin;
  1270. lastX = Math.max(minX, Math.min(GM_getValue('floatingButtonX', maxX), maxX));
  1271. lastY = Math.max(minY, Math.min(GM_getValue('floatingButtonY', maxY), maxY));
  1272. floatingButton.style.right = 'auto';
  1273. floatingButton.style.top = lastY + 'px';
  1274. floatingButton.style.left = lastX + 'px';
  1275. floatingButton.style.transform = 'scale(1)';
  1276. floatingButton.style.transform = 'scale(1)';
  1277. return floatingButton;
  1278. }
  1279. function createControls() {
  1280. const floatingButton = createConfigButton();
  1281. document.body.appendChild(floatingButton);
  1282.  
  1283. // 创建搜索框但不立即显示
  1284. createSearchBox();
  1285.  
  1286. // 添加快捷键监听
  1287. document.addEventListener('keydown', (e) => {
  1288. if (e.altKey && !e.ctrlKey && !e.shiftKey && e.key.toLowerCase() === 'f') {
  1289. e.preventDefault(); // 阻止默认行为
  1290. toggleSearchBox();
  1291. }
  1292. });
  1293. }
  1294.  
  1295. // 切换搜索框显示状态
  1296. function toggleSearchBox() {
  1297. const searchBox = document.querySelector('.search-box-container');
  1298. if (searchBox) {
  1299. searchBox.style.display = searchBox.style.display === 'none' ? 'flex' : 'none';
  1300. if (searchBox.style.display === 'flex') {
  1301. // 当显示搜索框时,自动聚焦到输入框
  1302. const searchInput = searchBox.querySelector('input');
  1303. if (searchInput) {
  1304. searchInput.focus();
  1305. }
  1306. }
  1307. }
  1308. }
  1309.  
  1310. // 创建搜索框
  1311. function createSearchBox(handleInput) {
  1312. const searchBoxContainer = document.createElement('div');
  1313. searchBoxContainer.className = 'search-box-container';
  1314. searchBoxContainer.style.cssText = `
  1315. position: fixed;
  1316. top: 10px;
  1317. left: 20px;
  1318. z-index: 9999;
  1319. background: rgba(28, 28, 28, 0.95);
  1320. padding: 15px 10px 10px;
  1321. border-radius: 8px;
  1322. box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
  1323. border: 1px solid #444;
  1324. display: none;
  1325. flex-direction: column;
  1326. gap: 8px;
  1327. `;
  1328.  
  1329. const searchRow = document.createElement('div');
  1330. searchRow.style.cssText = `
  1331. display: flex;
  1332. gap: 8px;
  1333. align-items: center;
  1334. `;
  1335.  
  1336. const navigationRow = document.createElement('div');
  1337. navigationRow.style.cssText = `
  1338. display: flex;
  1339. gap: 8px;
  1340. align-items: center;
  1341. `;
  1342.  
  1343. // 创建搜索输入框
  1344. const searchInput = document.createElement('input');
  1345. searchInput.type = 'text';
  1346. searchInput.placeholder = '输入关键词(用;分隔)';
  1347. searchInput.dataset.isSearchInput = 'true';
  1348. searchInput.style.cssText = `
  1349. width: 250px;
  1350. padding: 5px 10px;
  1351. border: 1px solid #666;
  1352. border-radius: 4px;
  1353. background: #2d2d2d;
  1354. color: #fff;
  1355. `;
  1356.  
  1357. // 添加搜索事件
  1358. searchInput.addEventListener('input', (e) => {
  1359. if (!e?.target?.value) return;
  1360.  
  1361. // 如果页面是繁体模式,则将输入转换为繁体
  1362. if (!STATE.pageSimplified) {
  1363. const cursorPosition = e.target.selectionStart;
  1364. const text = e.target.value;
  1365.  
  1366. requestAnimationFrame(() => {
  1367. try {
  1368. const convertedText = window.converter.toTraditional(text);
  1369. if (text === convertedText) return;
  1370.  
  1371. e.target.value = convertedText;
  1372.  
  1373. if (typeof cursorPosition === 'number') {
  1374. e.target.setSelectionRange(cursorPosition, cursorPosition);
  1375. }
  1376. } catch (error) {}
  1377. });
  1378. }
  1379. });
  1380.  
  1381. const searchButton = document.createElement('button');
  1382. searchButton.textContent = '搜索';
  1383. searchButton.style.cssText = `
  1384. padding: 5px 15px;
  1385. background: #4a90e2;
  1386. border: none;
  1387. border-radius: 4px;
  1388. color: #fff;
  1389. cursor: pointer;
  1390. transition: background 0.2s;
  1391. `;
  1392.  
  1393. // 添加预设下拉框
  1394. const presetSelectContainer = document.createElement('div');
  1395. presetSelectContainer.style.cssText = `
  1396. position: relative;
  1397. width: 120px;
  1398. `;
  1399.  
  1400. const presetInput = document.createElement('input');
  1401. presetInput.readOnly = true;
  1402. presetInput.placeholder = '选择预设';
  1403. presetInput.style.cssText = `
  1404. width: 100%;
  1405. padding: 5px;
  1406. background: #2d2d2d;
  1407. border: 1px solid #666;
  1408. border-radius: 4px;
  1409. color: #fff;
  1410. cursor: pointer;
  1411. `;
  1412.  
  1413. // 修改预设选择框的样式
  1414. const presetDropdown = document.createElement('div');
  1415. presetDropdown.style.cssText = `
  1416. display: none;
  1417. position: absolute;
  1418. top: 100%;
  1419. left: 0;
  1420. width: 200px;
  1421. max-height: 300px;
  1422. overflow-y: auto;
  1423. background: #2d2d2d;
  1424. border: 1px solid #666;
  1425. border-radius: 4px;
  1426. z-index: 10000;
  1427. margin-top: 4px;
  1428. padding-top: 30px; // 为搜索框留出空间
  1429. color: #fff; // 添加默认文字颜色
  1430. `;
  1431.  
  1432. // 添加预设搜索框
  1433. const presetSearchInput = document.createElement('input');
  1434. presetSearchInput.type = 'text';
  1435. presetSearchInput.placeholder = '搜索预设...';
  1436. presetSearchInput.style.cssText = `
  1437. position: absolute;
  1438. top: 0;
  1439. left: 0;
  1440. width: calc(100% - 16px);
  1441. margin: 8px;
  1442. padding: 4px 8px;
  1443. background: #3d3d3d;
  1444. border: 1px solid #666;
  1445. border-radius: 3px;
  1446. color: #fff;
  1447. font-size: 12px;
  1448. `;
  1449.  
  1450. // 阻止搜索框的点击事件冒泡,避免关闭下拉框
  1451. presetSearchInput.addEventListener('click', (e) => {
  1452. e.stopPropagation();
  1453. });
  1454.  
  1455. // 添加搜索框的输入事件
  1456. presetSearchInput.addEventListener('input', (e) => {
  1457. const searchText = e.target.value.toLowerCase();
  1458. const options = presetDropdown.querySelectorAll('.preset-option');
  1459. options.forEach(option => {
  1460. const name = option.querySelector('span').textContent.toLowerCase();
  1461. if (name.includes(searchText)) {
  1462. option.style.display = '';
  1463. } else {
  1464. option.style.display = 'none';
  1465. }
  1466. });
  1467. });
  1468.  
  1469. presetDropdown.appendChild(presetSearchInput);
  1470.  
  1471. // 添加滚动条样式
  1472. const styleSheet = document.createElement('style');
  1473. styleSheet.textContent = `
  1474. .preset-dropdown::-webkit-scrollbar {
  1475. width: 8px;
  1476. }
  1477. .preset-dropdown::-webkit-scrollbar-track {
  1478. background: #1a1a1a;
  1479. }
  1480. .preset-dropdown::-webkit-scrollbar-thumb {
  1481. background: #444;
  1482. border-radius: 4px;
  1483. }
  1484. .preset-dropdown::-webkit-scrollbar-thumb:hover {
  1485. background: #555;
  1486. }
  1487. `;
  1488. document.head.appendChild(styleSheet);
  1489.  
  1490. let selectedPresets = new Set();
  1491.  
  1492. // 修改预设选项的样式
  1493. function updatePresetOptions() {
  1494. // 保留搜索框
  1495. presetDropdown.innerHTML = '';
  1496. presetDropdown.appendChild(presetSearchInput);
  1497. presetSearchInput.value = ''; // 清空搜索框
  1498.  
  1499. // 创建分隔线
  1500. const createDivider = () => {
  1501. const divider = document.createElement('div');
  1502. divider.style.cssText = `
  1503. height: 1px;
  1504. background: #666;
  1505. margin: 5px 0;
  1506. `;
  1507. return divider;
  1508. };
  1509.  
  1510. // 创建预设选项
  1511. const createPresetOption = (name, keywords, isSelected) => {
  1512. const option = document.createElement('div');
  1513. option.className = 'preset-option'; // 添加类名以便搜索
  1514. option.style.cssText = `
  1515. padding: 6px 10px;
  1516. cursor: pointer;
  1517. display: flex;
  1518. align-items: center;
  1519. gap: 8px;
  1520. transition: all 0.2s;
  1521. color: #fff;
  1522. ${isSelected ? 'background: #2a4a6d;' : ''}
  1523. `;
  1524.  
  1525. const checkbox = document.createElement('input');
  1526. checkbox.type = 'checkbox';
  1527. checkbox.checked = isSelected;
  1528. checkbox.style.cssText = `
  1529. margin: 0;
  1530. cursor: pointer;
  1531. `;
  1532.  
  1533. const label = document.createElement('span');
  1534. label.textContent = name;
  1535. label.style.cssText = `
  1536. flex: 1;
  1537. overflow: hidden;
  1538. text-overflow: ellipsis;
  1539. white-space: nowrap;
  1540. color: #fff;
  1541. `;
  1542.  
  1543. option.appendChild(checkbox);
  1544. option.appendChild(label);
  1545.  
  1546. option.addEventListener('mouseover', () => {
  1547. option.style.backgroundColor = isSelected ? '#2a5a8d' : '#3d3d3d';
  1548. });
  1549.  
  1550. option.addEventListener('mouseout', () => {
  1551. option.style.backgroundColor = isSelected ? '#2a4a6d' : 'transparent';
  1552. });
  1553.  
  1554. option.addEventListener('click', (e) => {
  1555. e.stopPropagation();
  1556. checkbox.checked = !checkbox.checked;
  1557. if (checkbox.checked) {
  1558. selectedPresets.add(name);
  1559. } else {
  1560. selectedPresets.delete(name);
  1561. }
  1562. updateSelectedPresets();
  1563. // 重新渲染预设列表以更新顺序
  1564. updatePresetOptions();
  1565. });
  1566.  
  1567. return option;
  1568. };
  1569.  
  1570. // 获取所有预设并分类
  1571. const selectedOptions = [];
  1572. const unselectedOptions = [];
  1573. Object.entries(STATE.searchPresets).forEach(([name, keywords]) => {
  1574. const isSelected = selectedPresets.has(name);
  1575. const option = createPresetOption(name, keywords, isSelected);
  1576. if (isSelected) {
  1577. selectedOptions.push(option);
  1578. } else {
  1579. unselectedOptions.push(option);
  1580. }
  1581. });
  1582.  
  1583. // 添加已选择的预设
  1584. if (selectedOptions.length > 0) {
  1585. const selectedTitle = document.createElement('div');
  1586. selectedTitle.style.cssText = `
  1587. padding: 5px 10px;
  1588. color: #999;
  1589. font-size: 12px;
  1590. background: #262626;
  1591. `;
  1592. selectedTitle.textContent = '已选择的预设';
  1593. presetDropdown.appendChild(selectedTitle);
  1594. selectedOptions.forEach(option => presetDropdown.appendChild(option));
  1595. }
  1596.  
  1597. // 添加未选择的预设
  1598. if (selectedOptions.length > 0 && unselectedOptions.length > 0) {
  1599. presetDropdown.appendChild(createDivider());
  1600. }
  1601.  
  1602. if (unselectedOptions.length > 0) {
  1603. if (selectedOptions.length > 0) {
  1604. const unselectedTitle = document.createElement('div');
  1605. unselectedTitle.style.cssText = `
  1606. padding: 5px 10px;
  1607. color: #999;
  1608. font-size: 12px;
  1609. background: #262626;
  1610. `;
  1611. unselectedTitle.textContent = '未选择的预设';
  1612. presetDropdown.appendChild(unselectedTitle);
  1613. }
  1614. unselectedOptions.forEach(option => presetDropdown.appendChild(option));
  1615. }
  1616. }
  1617.  
  1618. function updateSelectedPresets() {
  1619. if (selectedPresets.size === 0) {
  1620. presetInput.value = '';
  1621. presetInput.placeholder = '选择预设';
  1622. } else {
  1623. const names = Array.from(selectedPresets).join(', ');
  1624. presetInput.value = names;
  1625. presetInput.placeholder = '';
  1626. }
  1627. }
  1628.  
  1629. function applySelectedPresets() {
  1630. if (selectedPresets.size === 0) return;
  1631.  
  1632. const keywords = Array.from(selectedPresets)
  1633. .map(name => STATE.searchPresets[name])
  1634. .join(';');
  1635.  
  1636. searchInput.value = keywords;
  1637.  
  1638. // 手动触发输入转换
  1639. if (!STATE.pageSimplified) {
  1640. try {
  1641. const convertedText = window.converter.toTraditional(keywords);
  1642. if (keywords !== convertedText) {
  1643. searchInput.value = convertedText;
  1644. }
  1645. } catch (error) {}
  1646. }
  1647.  
  1648. // 触发搜索
  1649. performSearch();
  1650. // 清空选择
  1651. selectedPresets.clear();
  1652. updateSelectedPresets();
  1653. }
  1654.  
  1655. // 点击输入框时显示/隐藏下拉框
  1656. presetInput.addEventListener('click', (e) => {
  1657. e.stopPropagation();
  1658. const isVisible = presetDropdown.style.display === 'block';
  1659. presetDropdown.style.display = isVisible ? 'none' : 'block';
  1660. if (!isVisible) {
  1661. updatePresetOptions();
  1662. // 聚焦搜索框
  1663. setTimeout(() => {
  1664. presetSearchInput.focus();
  1665. }, 0);
  1666. }
  1667. });
  1668.  
  1669. // 点击其他地方时隐藏下拉框
  1670. document.addEventListener('click', () => {
  1671. presetDropdown.style.display = 'none';
  1672. });
  1673.  
  1674. // 添加搜索事件
  1675. const performSearch = () => {
  1676. // 获取所有预设关键词
  1677. const presetKeywords = Array.from(selectedPresets)
  1678. .map(name => STATE.searchPresets[name])
  1679. .join(';');
  1680.  
  1681. // 获取输入框关键词
  1682. const inputKeywords = searchInput.value.trim();
  1683.  
  1684. // 合并关键词
  1685. const combinedKeywords = [presetKeywords, inputKeywords]
  1686. .filter(k => k) // 过滤掉空字符串
  1687. .join(';');
  1688.  
  1689. // 如果页面是繁体模式,则将关键词转换为繁体
  1690. let searchKeywords = combinedKeywords;
  1691. if (!STATE.pageSimplified) {
  1692. try {
  1693. searchKeywords = window.converter.toTraditional(combinedKeywords);
  1694. } catch (error) {
  1695. console.error('转换繁体失败:', error);
  1696. }
  1697. }
  1698.  
  1699. // 使用;或;作为分隔符
  1700. const keywords = searchKeywords.toLowerCase().split(/[;;]/);
  1701. // 过滤掉空字符串
  1702. const filteredKeywords = keywords.filter(k => k.trim());
  1703. if (!filteredKeywords.length) {
  1704. clearHighlights();
  1705. matchCounter.textContent = '';
  1706. return;
  1707. }
  1708. searchAndHighlight(filteredKeywords, matchCounter);
  1709. };
  1710.  
  1711. searchInput.addEventListener('keyup', (e) => {
  1712. if (e.key === 'Enter') {
  1713. performSearch();
  1714. }
  1715. });
  1716.  
  1717. searchButton.addEventListener('click', performSearch);
  1718.  
  1719. // 添加导航按钮
  1720. const prevButton = document.createElement('button');
  1721. prevButton.textContent = '上一个';
  1722. prevButton.style.cssText = `
  1723. padding: 5px 15px;
  1724. background: #2d2d2d;
  1725. border: 1px solid #666;
  1726. border-radius: 4px;
  1727. color: #fff;
  1728. cursor: pointer;
  1729. transition: background 0.2s;
  1730. flex: 1;
  1731. `;
  1732.  
  1733. const nextButton = document.createElement('button');
  1734. nextButton.textContent = '下一个';
  1735. nextButton.style.cssText = `
  1736. padding: 5px 15px;
  1737. background: #2d2d2d;
  1738. border: 1px solid #666;
  1739. border-radius: 4px;
  1740. color: #fff;
  1741. cursor: pointer;
  1742. transition: background 0.2s;
  1743. flex: 1;
  1744. `;
  1745.  
  1746. const matchCounter = document.createElement('span');
  1747. matchCounter.className = 'search-counter';
  1748. matchCounter.style.cssText = `
  1749. color: #fff;
  1750. font-size: 12px;
  1751. padding: 0 10px;
  1752. min-width: 60px;
  1753. text-align: center;
  1754. `;
  1755.  
  1756. // 添加导航事件
  1757. prevButton.addEventListener('click', () => {
  1758. navigateHighlight('prev');
  1759. });
  1760.  
  1761. nextButton.addEventListener('click', () => {
  1762. navigateHighlight('next');
  1763. });
  1764.  
  1765. // 添加hover效果
  1766. [searchButton, prevButton, nextButton].forEach(button => {
  1767. button.addEventListener('mouseover', () => {
  1768. button.style.background = button === searchButton ? '#357abd' : '#3d3d3d';
  1769. });
  1770. button.addEventListener('mouseout', () => {
  1771. button.style.background = button === searchButton ? '#4a90e2' : '#2d2d2d';
  1772. });
  1773. });
  1774.  
  1775. // 组装界面
  1776. presetSelectContainer.appendChild(presetInput);
  1777. presetSelectContainer.appendChild(presetDropdown);
  1778.  
  1779. searchRow.appendChild(presetSelectContainer);
  1780. searchRow.appendChild(searchInput);
  1781. searchRow.appendChild(searchButton);
  1782.  
  1783. navigationRow.appendChild(prevButton);
  1784. navigationRow.appendChild(matchCounter);
  1785. navigationRow.appendChild(nextButton);
  1786.  
  1787. searchBoxContainer.appendChild(searchRow);
  1788. searchBoxContainer.appendChild(navigationRow);
  1789.  
  1790. // 添加选项行
  1791. const optionsRow = document.createElement('div');
  1792. optionsRow.style.cssText = `
  1793. display: flex;
  1794. gap: 8px;
  1795. align-items: center;
  1796. padding: 0 5px;
  1797. `;
  1798.  
  1799. const showOnlyMatchedLabel = document.createElement('label');
  1800. showOnlyMatchedLabel.style.cssText = `
  1801. display: flex;
  1802. align-items: center;
  1803. gap: 5px;
  1804. color: #fff;
  1805. font-size: 12px;
  1806. cursor: pointer;
  1807. `;
  1808.  
  1809. const showOnlyMatchedCheckbox = document.createElement('input');
  1810. showOnlyMatchedCheckbox.type = 'checkbox';
  1811. showOnlyMatchedCheckbox.checked = STATE.showOnlyMatched;
  1812. showOnlyMatchedCheckbox.style.cssText = `
  1813. margin: 0;
  1814. cursor: pointer;
  1815. `;
  1816.  
  1817. showOnlyMatchedLabel.appendChild(showOnlyMatchedCheckbox);
  1818. showOnlyMatchedLabel.appendChild(document.createTextNode('只显示匹配项'));
  1819.  
  1820. showOnlyMatchedCheckbox.addEventListener('change', (e) => {
  1821. STATE.showOnlyMatched = e.target.checked;
  1822. GM_setValue('showOnlyMatched', STATE.showOnlyMatched);
  1823. if (STATE.matchedCards.length > 0) {
  1824. updateCardVisibility();
  1825. }
  1826. });
  1827.  
  1828. optionsRow.appendChild(showOnlyMatchedLabel);
  1829. searchBoxContainer.insertBefore(optionsRow, navigationRow);
  1830.  
  1831. document.body.appendChild(searchBoxContainer);
  1832.  
  1833. // 添加关闭按钮
  1834. const closeButton = document.createElement('button');
  1835. closeButton.textContent = '×';
  1836. closeButton.style.cssText = `
  1837. position: absolute;
  1838. top: -10px;
  1839. right: -10px;
  1840. width: 20px;
  1841. height: 20px;
  1842. line-height: 1;
  1843. padding: 0;
  1844. background: #2d2d2d;
  1845. border: 1px solid #666;
  1846. border-radius: 50%;
  1847. color: #999;
  1848. font-size: 16px;
  1849. font-weight: bold;
  1850. cursor: pointer;
  1851. display: flex;
  1852. align-items: center;
  1853. justify-content: center;
  1854. transition: all 0.2s;
  1855. z-index: 10000;
  1856. box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
  1857. `;
  1858.  
  1859. closeButton.addEventListener('mouseover', () => {
  1860. closeButton.style.color = '#fff';
  1861. closeButton.style.background = '#3d3d3d';
  1862. closeButton.style.borderColor = '#999';
  1863. });
  1864.  
  1865. closeButton.addEventListener('mouseout', () => {
  1866. closeButton.style.color = '#999';
  1867. closeButton.style.background = '#2d2d2d';
  1868. closeButton.style.borderColor = '#666';
  1869. });
  1870.  
  1871. closeButton.addEventListener('click', () => {
  1872. searchBoxContainer.style.display = 'none';
  1873. clearHighlights();
  1874. searchInput.value = '';
  1875. matchCounter.textContent = '';
  1876. // 不清除选择的预设
  1877. });
  1878.  
  1879. searchBoxContainer.insertBefore(closeButton, searchBoxContainer.firstChild);
  1880.  
  1881. // 添加键盘快捷键
  1882. document.addEventListener('keydown', (e) => {
  1883. if (e.key === 'F3' || (e.ctrlKey && e.key === 'g')) {
  1884. e.preventDefault();
  1885. if (e.shiftKey) {
  1886. navigateHighlight('prev');
  1887. } else {
  1888. navigateHighlight('next');
  1889. }
  1890. }
  1891. });
  1892.  
  1893. // 在搜索框显示时更新预设选项
  1894. const originalToggleSearchBox = toggleSearchBox;
  1895. toggleSearchBox = function() {
  1896. const searchBox = document.querySelector('.search-box-container');
  1897. if (searchBox) {
  1898. const isCurrentlyHidden = searchBox.style.display === 'none';
  1899. if (isCurrentlyHidden) {
  1900. updatePresetOptions();
  1901. // 不清除选择的预设
  1902. updateSelectedPresets();
  1903. }
  1904. searchBox.style.display = isCurrentlyHidden ? 'flex' : 'none';
  1905. if (isCurrentlyHidden) {
  1906. const searchInput = searchBox.querySelector('input[type="text"]');
  1907. if (searchInput) {
  1908. searchInput.focus();
  1909. }
  1910. }
  1911. }
  1912. };
  1913.  
  1914. return updatePresetOptions;
  1915. }
  1916.  
  1917. // 清除所有高亮
  1918. function clearHighlights() {
  1919. const highlights = document.querySelectorAll('.st-highlight');
  1920. highlights.forEach(highlight => {
  1921. const parent = highlight.parentNode;
  1922. parent.replaceChild(document.createTextNode(highlight.textContent), highlight);
  1923. });
  1924.  
  1925. // 清除所有高亮样式
  1926. document.querySelectorAll('.current-highlight, .matched-card').forEach(card => {
  1927. card.classList.remove('current-highlight', 'matched-card');
  1928. });
  1929.  
  1930. // 重置导航状态
  1931. STATE.matchedCards = [];
  1932. STATE.currentMatchIndex = -1;
  1933.  
  1934. // 恢复所有卡片的可见性
  1935. const allCards = document.querySelectorAll('.row[data-id]');
  1936. allCards.forEach(card => {
  1937. card.style.display = '';
  1938. });
  1939. }
  1940.  
  1941. // 修改 searchAndHighlight 函数中的关键词处理部分
  1942. function escapeRegExp(string) {
  1943. return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  1944. }
  1945.  
  1946. function searchAndHighlight(keywords, matchCounter) {
  1947. clearHighlights();
  1948.  
  1949. const itemCards = document.querySelectorAll('.row[data-id]');
  1950. STATE.matchedCards = [];
  1951. let hasMatch = false;
  1952.  
  1953. // 预处理关键词,处理特殊字符和通配符
  1954. const processedKeywords = keywords.map(keyword => {
  1955. keyword = keyword.trim();
  1956. // 处理带条件的通配符
  1957. // 匹配模式:(&>2) 或 (&>=2) 或 (&<2) 或 (&<=2) 或 (&=2)
  1958. // 或带加号的版本:+(&>2) 等
  1959. const conditionalPattern = /(\(?&(>=|<=|>|<|=)(\d+)\)?)/;
  1960. if (conditionalPattern.test(keyword)) {
  1961. const match = keyword.match(conditionalPattern);
  1962. const fullMatch = match[0];
  1963. const operator = match[2];
  1964. const targetNum = parseInt(match[3]);
  1965. // 将关键词分成前后两部分
  1966. const [before, after] = keyword.split(fullMatch);
  1967. // 构建正则表达式和验证函数
  1968. const numPattern = '(\\d+)';
  1969. const beforePattern = before ? escapeRegExp(before) : '';
  1970. const afterPattern = after ? escapeRegExp(after) : '';
  1971. return {
  1972. pattern: beforePattern + numPattern + afterPattern,
  1973. validate: (foundNum) => {
  1974. const num = parseInt(foundNum);
  1975. switch(operator) {
  1976. case '>': return num > targetNum;
  1977. case '>=': return num >= targetNum;
  1978. case '<': return num < targetNum;
  1979. case '<=': return num <= targetNum;
  1980. case '=': return num === targetNum;
  1981. default: return false;
  1982. }
  1983. }
  1984. };
  1985. }
  1986. // 处理简单通配符
  1987. if (keyword.includes('&')) {
  1988. // 处理带加号的通配符
  1989. if (keyword.includes('+&')) {
  1990. keyword = escapeRegExp(keyword).replace(/\\\+&/g, '\\+[0-9]+');
  1991. } else {
  1992. // 处理不带加号的通配符
  1993. keyword = escapeRegExp(keyword).replace(/&/g, '[0-9]+');
  1994. }
  1995. } else {
  1996. // 处理其他特殊字符
  1997. keyword = escapeRegExp(keyword).replace(/\\\+/g, '[++]');
  1998. }
  1999. return { pattern: keyword };
  2000. }).filter(k => k);
  2001.  
  2002. itemCards.forEach(card => {
  2003. const cardText = card.textContent;
  2004. const matches = processedKeywords.map(keyword => {
  2005. if (!keyword.validate) {
  2006. // 简单模式匹配
  2007. const regex = new RegExp(keyword.pattern, 'i');
  2008. return regex.test(cardText);
  2009. } else {
  2010. // 条件模式匹配
  2011. const regex = new RegExp(keyword.pattern, 'i');
  2012. const match = cardText.match(regex);
  2013. if (!match) return false;
  2014. // 提取数字并验证条件
  2015. const foundNum = match[1];
  2016. return keyword.validate(foundNum);
  2017. }
  2018. });
  2019. const allMatch = matches.every(match => match);
  2020.  
  2021. if (allMatch) {
  2022. hasMatch = true;
  2023. STATE.matchedCards.push(card);
  2024. highlightKeywords(card, processedKeywords.map(k => k.pattern));
  2025. } else if (STATE.showOnlyMatched) {
  2026. card.style.display = 'none';
  2027. }
  2028. });
  2029.  
  2030. if (!hasMatch) {
  2031. alert('未找到匹配的结果');
  2032. if (matchCounter) {
  2033. matchCounter.textContent = '0/0';
  2034. }
  2035. } else {
  2036. STATE.currentMatchIndex = 0;
  2037. updateHighlightNavigation();
  2038. updateCardVisibility();
  2039. }
  2040. }
  2041.  
  2042. // 修改 highlightKeywords 函数
  2043. function highlightKeywords(element, patterns) {
  2044. const walker = document.createTreeWalker(
  2045. element,
  2046. NodeFilter.SHOW_TEXT,
  2047. {
  2048. acceptNode: function(node) {
  2049. if (node.parentNode.nodeName === 'SCRIPT' ||
  2050. node.parentNode.nodeName === 'STYLE' ||
  2051. node.parentNode.classList.contains('st-highlight')) {
  2052. return NodeFilter.FILTER_REJECT;
  2053. }
  2054.  
  2055. const text = node.textContent;
  2056. const containsAnyKeyword = patterns.some(pattern => {
  2057. const regex = new RegExp(pattern, 'i');
  2058. return regex.test(text);
  2059. });
  2060.  
  2061. return containsAnyKeyword ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
  2062. }
  2063. }
  2064. );
  2065.  
  2066. const nodes = [];
  2067. let node;
  2068. while (node = walker.nextNode()) {
  2069. nodes.push(node);
  2070. }
  2071.  
  2072. nodes.forEach(textNode => {
  2073. let text = textNode.textContent;
  2074. let tempText = text;
  2075.  
  2076. patterns.forEach(pattern => {
  2077. const regex = new RegExp(`(${pattern})`, 'gi');
  2078. if (regex.test(text)) {
  2079. tempText = tempText.replace(regex, (match) => {
  2080. return `<span class="st-highlight">${match}</span>`;
  2081. });
  2082. }
  2083. });
  2084.  
  2085. if (tempText !== text) {
  2086. const tempDiv = document.createElement('div');
  2087. tempDiv.innerHTML = tempText;
  2088. const fragment = document.createDocumentFragment();
  2089. while (tempDiv.firstChild) {
  2090. fragment.appendChild(tempDiv.firstChild);
  2091. }
  2092. textNode.parentNode.replaceChild(fragment, textNode);
  2093. }
  2094. });
  2095. }
  2096.  
  2097. // 更新高亮导航
  2098. function updateHighlightNavigation() {
  2099. const matchCounter = document.querySelector('.search-counter');
  2100. if (!matchCounter) return;
  2101.  
  2102. // 更新计数器
  2103. matchCounter.textContent = `${STATE.currentMatchIndex + 1}/${STATE.matchedCards.length}`;
  2104.  
  2105. // 移除之前的当前高亮
  2106. document.querySelectorAll('.current-highlight, .matched-card').forEach(card => {
  2107. card.classList.remove('current-highlight', 'matched-card');
  2108. });
  2109.  
  2110. // 添加新的当前高亮
  2111. const currentCard = STATE.matchedCards[STATE.currentMatchIndex];
  2112. if (currentCard) {
  2113. currentCard.classList.add('current-highlight');
  2114. // 滚动到当前卡片
  2115. currentCard.scrollIntoView({
  2116. behavior: 'smooth',
  2117. block: 'center'
  2118. });
  2119. }
  2120. }
  2121.  
  2122. // 导航到上一个/下一个高亮
  2123. function navigateHighlight(direction) {
  2124. if (STATE.matchedCards.length === 0) return;
  2125.  
  2126. if (direction === 'next') {
  2127. STATE.currentMatchIndex = (STATE.currentMatchIndex + 1) % STATE.matchedCards.length;
  2128. } else {
  2129. STATE.currentMatchIndex = (STATE.currentMatchIndex - 1 + STATE.matchedCards.length) % STATE.matchedCards.length;
  2130. }
  2131.  
  2132. updateHighlightNavigation();
  2133. }
  2134.  
  2135. // 修改样式
  2136. const style = document.createElement('style');
  2137. style.textContent = `
  2138. .current-highlight {
  2139. background-color: rgba(255, 215, 0, 0.3) !important;
  2140. }
  2141. .matched-card {
  2142. background-color: rgba(255, 215, 0, 0.1) !important;
  2143. }
  2144. .st-highlight {
  2145. background-color: #ffd700;
  2146. color: #000;
  2147. border-radius: 2px;
  2148. padding: 0 2px;
  2149. }
  2150. `;
  2151. document.head.appendChild(style);
  2152. function watchSearchResults(converter) {
  2153. let lastUrl = location.href;
  2154. const urlObserver = setInterval(() => {
  2155. if (location.href !== lastUrl) {
  2156. lastUrl = location.href;
  2157. STATE.originalTexts = new WeakMap();
  2158. setTimeout(() => {
  2159. convertPageText(converter);
  2160. }, 500);
  2161. }
  2162. }, 100);
  2163. // 监视搜索结果变化
  2164. const resultObserver = new MutationObserver((mutations) => {
  2165. let needsConversion = false;
  2166. for (const mutation of mutations) {
  2167. if (mutation.type === 'childList' || mutation.type === 'characterData') {
  2168. needsConversion = true;
  2169. break;
  2170. }
  2171. }
  2172. if (needsConversion) {
  2173. setTimeout(() => convertPageText(converter), 100);
  2174. }
  2175. });
  2176. const resultsContainer = document.querySelector('.results-container');
  2177. if (resultsContainer) {
  2178. resultObserver.observe(resultsContainer, {
  2179. childList: true,
  2180. subtree: true,
  2181. characterData: true
  2182. });
  2183. }
  2184. }
  2185. function findReactInstance(element) {
  2186. const key = Object.keys(element).find(key => key.startsWith('__reactFiber$'));
  2187. return key ? element[key] : null;
  2188. }
  2189. function findLoadMoreHandler() {
  2190. const loadMoreBtn = document.querySelector('.load-more-btn');
  2191. if (!loadMoreBtn) {
  2192. console.log('未找到加载更多按钮');
  2193. return null;
  2194. }
  2195. // 尝试获取React实例
  2196. const instance = findReactInstance(loadMoreBtn);
  2197. if (!instance) {
  2198. console.log('未找到React实例');
  2199. return null;
  2200. }
  2201. // 遍历查找onClick处理函数
  2202. let current = instance;
  2203. while (current) {
  2204. if (current.memoizedProps && current.memoizedProps.onClick) {
  2205. return current.memoizedProps.onClick;
  2206. }
  2207. current = current.return;
  2208. }
  2209. console.log('未找到onClick处理函数');
  2210. return null;
  2211. }
  2212. function clickLoadMoreIfExists() {
  2213. // 使用正确的选择器
  2214. const loadMoreBtn = document.querySelector('.btn.load-more-btn');
  2215. if (!loadMoreBtn) {
  2216. console.log('未找到加载更多按钮');
  2217. return false;
  2218. }
  2219. const results = document.querySelectorAll('.resultset, .trade-result, [class*="result-item"]');
  2220. const currentResultCount = results.length;
  2221. if (currentResultCount >= 100) {
  2222. return false;
  2223. }
  2224. try {
  2225. // 尝试多种方式触发点击
  2226. // 1. 原生点击
  2227. loadMoreBtn.click();
  2228. // 2. 模拟鼠标事件序列
  2229. ['mousedown', 'mouseup', 'click'].forEach(eventType => {
  2230. const event = new MouseEvent(eventType, {
  2231. bubbles: true,
  2232. cancelable: true,
  2233. buttons: 1
  2234. });
  2235. loadMoreBtn.dispatchEvent(event);
  2236. });
  2237. // 3. 尝试点击内部的span
  2238. const spanInButton = loadMoreBtn.querySelector('span');
  2239. if (spanInButton) {
  2240. spanInButton.click();
  2241. ['mousedown', 'mouseup', 'click'].forEach(eventType => {
  2242. const event = new MouseEvent(eventType, {
  2243. bubbles: true,
  2244. cancelable: true,
  2245. buttons: 1
  2246. });
  2247. spanInButton.dispatchEvent(event);
  2248. });
  2249. }
  2250. // 4. 使用 HTMLElement 的 click 方法
  2251. HTMLElement.prototype.click.call(loadMoreBtn);
  2252. return true;
  2253. } catch (error) {
  2254. console.log('触发加载更多时出错:', error);
  2255. return false;
  2256. }
  2257. }
  2258. function autoLoadAllResults() {
  2259. let attempts = 0;
  2260. const maxAttempts = 20;
  2261. let lastResultCount = 0;
  2262. function tryLoadMore() {
  2263. const results = document.querySelectorAll('.resultset');
  2264. const currentResultCount = results.length;
  2265. if (currentResultCount >= 100 || attempts >= maxAttempts ||
  2266. (currentResultCount === lastResultCount && attempts > 0)) {
  2267. return;
  2268. }
  2269. if (clickLoadMoreIfExists()) {
  2270. lastResultCount = currentResultCount;
  2271. attempts++;
  2272. // 修改加载更多的处理方式
  2273. setTimeout(() => {
  2274. // 确保新内容加载后计算DPS
  2275. const newResults = document.querySelectorAll('.row[data-id]').length;
  2276. if (newResults > currentResultCount) {
  2277. calculateDPS();
  2278. }
  2279. tryLoadMore();
  2280. }, 1000);
  2281. }
  2282. }
  2283. setTimeout(tryLoadMore, 1000);
  2284. }
  2285. // 检查URL是否是搜索结果页面
  2286. function isSearchResultPage() {
  2287. const isPOE2Trade = window.location.href.includes('pathofexile.com/trade2/search/poe2');
  2288. const hasResults = document.querySelector('.results-container, .trade-results, .search-results, [class*="results"]') !== null;
  2289. return isPOE2Trade && hasResults;
  2290. }
  2291. // 将这些函数移到init函数外部
  2292. function createDPSPanel() {
  2293. const panel = document.createElement('div');
  2294. panel.id = 'dps-sort-panel';
  2295. panel.style.cssText = `
  2296. position: fixed;
  2297. right: 20px;
  2298. top: 50%;
  2299. transform: translateY(-50%);
  2300. background: rgba(28, 28, 28, 0.95);
  2301. padding: 8px;
  2302. border-radius: 6px;
  2303. box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
  2304. border: 1px solid #444;
  2305. width: 200px; // 增加宽度以适应价格显示
  2306. max-height: 60vh;
  2307. z-index: 9997;
  2308. display: none;
  2309. `;
  2310.  
  2311. // 添加标题
  2312. const title = document.createElement('div');
  2313. title.style.cssText = `
  2314. font-weight: bold;
  2315. color: #FFD700;
  2316. margin-bottom: 6px;
  2317. padding-bottom: 3px;
  2318. border-bottom: 1px solid #444;
  2319. display: flex;
  2320. justify-content: space-between;
  2321. align-items: center;
  2322. cursor: pointer;
  2323. font-size: 14px;
  2324. user-select: none;
  2325. height: 18px;
  2326. `;
  2327. // 添加展开/收起指示器
  2328. const indicator = document.createElement('span');
  2329. indicator.textContent = '▼';
  2330. indicator.style.marginRight = '3px';
  2331. indicator.style.fontSize = '10px';
  2332. indicator.id = 'integrated-panel-indicator';
  2333. const titleText = document.createElement('span');
  2334. titleText.textContent = 'DPS';
  2335. const titleLeft = document.createElement('div');
  2336. titleLeft.style.display = 'flex';
  2337. titleLeft.style.alignItems = 'center';
  2338. titleLeft.appendChild(indicator);
  2339. titleLeft.appendChild(titleText);
  2340.  
  2341. title.appendChild(titleLeft);
  2342.  
  2343. // 添加关闭按钮
  2344. const closeBtn = document.createElement('button');
  2345. closeBtn.textContent = '×';
  2346. closeBtn.style.cssText = `
  2347. background: none;
  2348. border: none;
  2349. color: #999;
  2350. font-size: 20px;
  2351. cursor: pointer;
  2352. padding: 0 5px;
  2353. `;
  2354. closeBtn.onclick = (e) => {
  2355. e.stopPropagation();
  2356. panel.style.display = 'none';
  2357. };
  2358. title.appendChild(closeBtn);
  2359.  
  2360. // 添加内容容器
  2361. const content = document.createElement('div');
  2362. content.id = 'dps-sort-content';
  2363. content.style.cssText = `
  2364. max-height: calc(60vh - 35px);
  2365. overflow-y: auto;
  2366. transition: max-height 0.3s ease-out;
  2367. padding-right: 2px;
  2368. `;
  2369.  
  2370. // 添加展开/收起功能
  2371. let isExpanded = true;
  2372. title.onclick = () => {
  2373. isExpanded = !isExpanded;
  2374. content.style.maxHeight = isExpanded ? 'calc(60vh - 35px)' : '0';
  2375. content.style.overflow = isExpanded ? 'auto' : 'hidden';
  2376. indicator.textContent = isExpanded ? '▼' : '▶';
  2377.  
  2378. // 调整护盾面板位置
  2379. const shieldPanel = document.getElementById('shield-sort-panel');
  2380. if (shieldPanel) {
  2381. if (isExpanded) {
  2382. shieldPanel.style.transform = 'translateY(calc(-50% + 200px))';
  2383. } else {
  2384. shieldPanel.style.transform = 'translateY(calc(-50% + 50px))';
  2385. }
  2386. }
  2387. };
  2388.  
  2389. // 添加滚动条样式
  2390. const style = document.createElement('style');
  2391. style.textContent = `
  2392. #dps-sort-content::-webkit-scrollbar {
  2393. width: 6px;
  2394. }
  2395. #dps-sort-content::-webkit-scrollbar-track {
  2396. background: #1a1a1a;
  2397. }
  2398. #dps-sort-content::-webkit-scrollbar-thumb {
  2399. background: #444;
  2400. border-radius: 3px;
  2401. }
  2402. #dps-sort-content::-webkit-scrollbar-thumb:hover {
  2403. background: #555;
  2404. }
  2405. .dps-item {
  2406. padding: 4px 8px;
  2407. margin: 1px 0;
  2408. background: #2d2d2d;
  2409. border-radius: 3px;
  2410. cursor: pointer;
  2411. transition: background 0.2s;
  2412. display: flex;
  2413. justify-content: space-between;
  2414. align-items: center;
  2415. gap: 8px;
  2416. font-size: 13px;
  2417. white-space: nowrap;
  2418. user-select: none;
  2419. line-height: 1.2;
  2420. }
  2421. .dps-item:hover {
  2422. background: #3d3d3d;
  2423. }
  2424. .dps-item:last-child {
  2425. margin-bottom: 0;
  2426. }
  2427. .dps-value {
  2428. color: #FFD700;
  2429. font-weight: bold;
  2430. }
  2431. .price-value {
  2432. color: #8acdff;
  2433. font-size: 12px;
  2434. text-align: right;
  2435. }
  2436. `;
  2437.  
  2438. panel.appendChild(title);
  2439. panel.appendChild(content);
  2440. document.head.appendChild(style);
  2441. document.body.appendChild(panel);
  2442.  
  2443. return panel;
  2444. }
  2445.  
  2446. // 将convertCurrencyText函数移到全局作用域
  2447. function convertCurrencyText(currencyText) {
  2448. if (!currencyText) return '';
  2449. if (currencyText.includes('Exalted') || currencyText.includes('exalted')) return 'E';
  2450. if (currencyText.includes('Divine') || currencyText.includes('divine')) return 'D';
  2451. if (currencyText.includes('Chaos') || currencyText.includes('chaos')) return 'C';
  2452. if (currencyText.includes('崇高')) return 'E';
  2453. if (currencyText.includes('神圣')) return 'D';
  2454. if (currencyText.includes('混沌')) return 'C';
  2455. return currencyText; // 其他货币保持原样
  2456. }
  2457.  
  2458. function updateDPSPanel() {
  2459. try {
  2460. console.log('Updating DPS panel...');
  2461. // 获取或创建面板
  2462. const panel = document.getElementById('integrated-sort-panel') || createIntegratedPanel();
  2463. const content = document.getElementById('dps-sort-content');
  2464. if (!content) {
  2465. console.error('DPS sort content not found');
  2466. return;
  2467. }
  2468.  
  2469. // 清空现有内容
  2470. content.innerHTML = '';
  2471. // 清除可能存在的旧数据属性
  2472. content.removeAttribute('data-shield-content');
  2473. content.removeAttribute('data-evasion-content');
  2474. content.removeAttribute('data-armour-content');
  2475. content.removeAttribute('data-defence-content');
  2476.  
  2477. const items = document.querySelectorAll('.row[data-id]');
  2478. const dpsData = [];
  2479.  
  2480. items.forEach(item => {
  2481. const dpsDisplay = item.querySelector('.dps-display');
  2482. if (dpsDisplay) {
  2483. const dps = parseInt(dpsDisplay.textContent.replace('DPS: ', ''));
  2484. // 修改价格获取逻辑
  2485. const priceElement = item.querySelector('.price [data-field="price"]');
  2486. let price = '未标价';
  2487. if (priceElement) {
  2488. const amount = priceElement.querySelector('span:not(.price-label):not(.currency-text)');
  2489. const currencyText = priceElement.querySelector('.currency-text');
  2490. if (amount && currencyText) {
  2491. // 获取数量和转换后的货币类型
  2492. const amountText = amount.textContent.trim();
  2493. const currency = currencyText.querySelector('span')?.textContent || currencyText.textContent;
  2494. const simpleCurrency = convertCurrencyText(currency);
  2495. price = `${amountText}${simpleCurrency}`;
  2496. }
  2497. }
  2498.  
  2499. dpsData.push({
  2500. dps,
  2501. price,
  2502. element: item
  2503. });
  2504. }
  2505. });
  2506.  
  2507. // 如果没有DPS数据,隐藏DPS选项卡
  2508. const dpsTab = panel.querySelector('[data-tab="dps"]');
  2509. if (dpsData.length === 0) {
  2510. if (dpsTab) dpsTab.style.display = 'none';
  2511.  
  2512. // 如果护盾选项卡也是隐藏的,则隐藏整个面板
  2513. const shieldTab = panel.querySelector('[data-tab="shield"]');
  2514. const evasionTab = panel.querySelector('[data-tab="evasion"]');
  2515. const armourTab = panel.querySelector('[data-tab="armour"]');
  2516. const defenceTab = panel.querySelector('[data-tab="defence"]');
  2517. // 检查是否所有选项卡都隐藏了
  2518. if ((shieldTab && shieldTab.style.display === 'none') &&
  2519. (evasionTab && evasionTab.style.display === 'none') &&
  2520. (armourTab && armourTab.style.display === 'none') &&
  2521. (defenceTab && defenceTab.style.display === 'none')) {
  2522. panel.style.display = 'none';
  2523. }
  2524. // 移除自动切换到护盾选项卡的代码,保留当前选项卡状态
  2525. return;
  2526. } else {
  2527. // 有DPS数据,确保DPS选项卡可见
  2528. if (dpsTab) dpsTab.style.display = '';
  2529. }
  2530.  
  2531. // 按DPS从高到低排序
  2532. dpsData.sort((a, b) => b.dps - a.dps);
  2533.  
  2534. // 更新面板内容
  2535. dpsData.forEach(({dps, price, element}) => {
  2536. const dpsItem = document.createElement('div');
  2537. dpsItem.className = 'dps-item';
  2538. // 创建DPS显示
  2539. const dpsText = document.createElement('span');
  2540. dpsText.className = 'dps-value';
  2541. dpsText.textContent = dps.toString();
  2542. // 创建价格显示
  2543. const priceText = document.createElement('span');
  2544. priceText.className = 'price-value';
  2545. priceText.textContent = price;
  2546. // 添加到DPS项
  2547. dpsItem.appendChild(dpsText);
  2548. dpsItem.appendChild(priceText);
  2549. dpsItem.onclick = () => {
  2550. element.scrollIntoView({ behavior: 'smooth', block: 'center' });
  2551. // 添加高亮效果
  2552. element.style.transition = 'background-color 0.3s';
  2553. element.style.backgroundColor = 'rgba(255, 215, 0, 0.2)';
  2554. setTimeout(() => {
  2555. element.style.backgroundColor = '';
  2556. }, 1500);
  2557. };
  2558. content.appendChild(dpsItem);
  2559. });
  2560.  
  2561. panel.style.display = 'block';
  2562. } catch (error) {
  2563. console.error('Error updating DPS panel:', error);
  2564. }
  2565. }
  2566.  
  2567. function calculateDPS() {
  2568. console.log('Calculating DPS...'); // 添加调试日志
  2569. const items = document.querySelectorAll('.row[data-id]');
  2570. console.log('Found items:', items.length); // 添加调试日志
  2571. items.forEach(item => {
  2572. // 查找已有的DPS显示,如果存在则跳过
  2573. if (item.querySelector('.dps-display')) return;
  2574.  
  2575. // 获取元素伤害
  2576. const edamageSpan = item.querySelector('span[data-field="edamage"]');
  2577. if (!edamageSpan) return;
  2578.  
  2579. // 初始化伤害值
  2580. let minTotal = 0;
  2581. let maxTotal = 0;
  2582.  
  2583. // 获取所有元素伤害值
  2584. const damages = {
  2585. fire: edamageSpan.querySelector('.colourFireDamage'),
  2586. lightning: edamageSpan.querySelector('.colourLightningDamage'),
  2587. cold: edamageSpan.querySelector('.colourColdDamage')
  2588. };
  2589.  
  2590. // 处理每种元素伤害
  2591. Object.values(damages).forEach(dmg => {
  2592. if (dmg) {
  2593. const [min, max] = dmg.textContent.split('-').map(Number);
  2594. if (!isNaN(min) && !isNaN(max)) {
  2595. minTotal += min;
  2596. maxTotal += max;
  2597. }
  2598. }
  2599. });
  2600.  
  2601. // 获取元素增伤
  2602. let elementInc = 1;
  2603. const elementIncSpan = item.querySelector('span[data-field="stat.explicit.stat_387439868"]');
  2604. if (elementIncSpan) {
  2605. const incMatch = elementIncSpan.textContent.match(/(\d+)%/);
  2606. if (incMatch) {
  2607. elementInc = 1 + (parseInt(incMatch[1]) / 100);
  2608. }
  2609. }
  2610.  
  2611. // 获取攻击速度
  2612. let aps = 1;
  2613. const apsSpan = item.querySelector('span[data-field="aps"]');
  2614. if (apsSpan) {
  2615. let apsValue = apsSpan.querySelector('.colourDefault');
  2616. if (!apsValue) {
  2617. apsValue = apsSpan.querySelector('.colourAugmented');
  2618. }
  2619. if (apsValue) {
  2620. aps = parseFloat(apsValue.textContent) || 1;
  2621. }
  2622. }
  2623.  
  2624. // 计算DPS
  2625. const dps = ((minTotal + maxTotal) / 2) * elementInc * aps;
  2626. console.log('minTotal:', minTotal);
  2627. console.log('maxTotal:', maxTotal);
  2628. console.log('elementInc:', elementInc);
  2629. console.log('aps:', aps);
  2630. console.log('dps:', dps);
  2631.  
  2632. // 创建DPS显示元素
  2633. const dpsDisplay = document.createElement('span');
  2634. dpsDisplay.className = 'dps-display';
  2635. dpsDisplay.style.cssText = `
  2636. color: #FFD700;
  2637. font-weight: bold;
  2638. margin-left: 10px;
  2639. `;
  2640. dpsDisplay.textContent = `DPS: ${Math.round(dps)}`;
  2641.  
  2642. // 将DPS显示添加到元素伤害后面
  2643. edamageSpan.appendChild(dpsDisplay);
  2644. });
  2645.  
  2646. // 更新DPS排序面板
  2647. updateDPSPanel();
  2648. }
  2649.  
  2650. // 创建一个防抖函数来避免过于频繁的计算
  2651. function debounce(func, wait) {
  2652. let timeout;
  2653. return function executedFunction(...args) {
  2654. const later = () => {
  2655. clearTimeout(timeout);
  2656. func(...args);
  2657. };
  2658. clearTimeout(timeout);
  2659. timeout = setTimeout(later, wait);
  2660. };
  2661. }
  2662.  
  2663. async function init() {
  2664. /**
  2665. * 初始化脚本,设置转换器、观察器和UI元素
  2666. */
  2667. try {
  2668. // 等待页面完全加载
  2669. await new Promise(resolve => setTimeout(resolve, 100));
  2670.  
  2671. // 等待OpenCC库加载
  2672. const OpenCC = await waitForOpenCC();
  2673. console.log('OpenCC已加载');
  2674.  
  2675. // 创建转换器
  2676. const converter = createConverters(OpenCC);
  2677. window.converter = converter;
  2678.  
  2679. // 创建输入处理函数
  2680. const handleInput = createInputHandler(converter);
  2681.  
  2682. // 为现有输入元素添加事件监听器
  2683. attachInputListener(handleInput);
  2684.  
  2685. // 创建观察器监听DOM变化
  2686. const observer = createObserver(handleInput, converter);
  2687. observer.observe(document.body, {
  2688. childList: true,
  2689. subtree: true
  2690. });
  2691.  
  2692. // 创建控制面板
  2693. createControls();
  2694.  
  2695. // 创建配置按钮
  2696. createConfigButton();
  2697.  
  2698. // 创建配置模态框
  2699. createConfigModal();
  2700.  
  2701. // 设置配置模态框事件
  2702. setupConfigModalEvents();
  2703.  
  2704. // 创建搜索框
  2705. createSearchBox(handleInput);
  2706.  
  2707. // 如果启用了页面简体化,转换页面文本
  2708. if (STATE.pageSimplified) {
  2709. convertPageText(converter);
  2710. }
  2711.  
  2712. // 监视搜索结果
  2713. watchSearchResults(converter);
  2714.  
  2715. // 定期转换页面文本
  2716. setInterval(() => {
  2717. if (STATE.pageSimplified) {
  2718. convertPageText(converter);
  2719. }
  2720. }, 1000);
  2721.  
  2722. // 使用防抖包装calculateDPS和calculateShield
  2723. const debouncedCalculateDPS = debounce(calculateDPS, 200);
  2724. const debouncedCalculateShield = debounce(calculateShield, 200);
  2725. const debouncedCalculateEvasion = debounce(calculateEvasion, 200);
  2726. const debouncedCalculateArmour = debounce(calculateArmour, 200);
  2727. const debouncedCalculateDefence = debounce(calculateDefence, 200);
  2728.  
  2729. // 同时计算DPS、护盾、闪避、护甲和总防御的防抖函数
  2730. const debouncedCalculate = debounce(() => {
  2731. calculateDPS();
  2732. calculateShield();
  2733. calculateEvasion();
  2734. calculateArmour();
  2735. calculateDefence();
  2736. }, 200);
  2737. // 监听搜索按钮点击事件
  2738. const setupSearchButtonListener = () => {
  2739. // 使用MutationObserver监听搜索按钮的出现
  2740. const searchBtnObserver = new MutationObserver((mutations, observer) => {
  2741. const searchBtn = document.querySelector('.controls-center .search-btn');
  2742. if (searchBtn) {
  2743. console.log('搜索按钮已找到,添加点击事件监听器');
  2744. searchBtn.addEventListener('click', () => {
  2745. console.log('搜索按钮被点击,触发计算...');
  2746. // 延迟执行计算,确保搜索结果已加载
  2747. setTimeout(() => {
  2748. debouncedCalculate();
  2749. }, 1000);
  2750. });
  2751. observer.disconnect(); // 找到按钮后停止观察
  2752. }
  2753. });
  2754. // 开始观察文档
  2755. searchBtnObserver.observe(document.body, {
  2756. childList: true,
  2757. subtree: true
  2758. });
  2759. // 立即检查一次,以防按钮已经存在
  2760. const searchBtn = document.querySelector('.controls-center .search-btn');
  2761. if (searchBtn) {
  2762. console.log('搜索按钮已存在,添加点击事件监听器');
  2763. searchBtn.addEventListener('click', () => {
  2764. console.log('搜索按钮被点击,触发计算...');
  2765. // 延迟执行计算,确保搜索结果已加载
  2766. setTimeout(() => {
  2767. debouncedCalculate();
  2768. }, 1000);
  2769. });
  2770. }
  2771. };
  2772. // 设置搜索按钮监听器
  2773. setupSearchButtonListener();
  2774.  
  2775. // 创建一个更强大的观察器来监视DOM变化
  2776. const resultsObserver = new MutationObserver((mutations) => {
  2777. let hasNewContent = false;
  2778. mutations.forEach(mutation => {
  2779. // 检查是否有新的结果项被添加
  2780. if (mutation.type === 'childList' &&
  2781. mutation.addedNodes.length > 0 &&
  2782. Array.from(mutation.addedNodes).some(node =>
  2783. node.nodeType === 1 && // 元素节点
  2784. (node.classList?.contains('row') || node.querySelector?.('.row[data-id]'))
  2785. )) {
  2786. hasNewContent = true;
  2787. }
  2788. });
  2789.  
  2790. if (hasNewContent) {
  2791. console.log('New content detected, calculating DPS, Shield, Evasion and Armour...'); // 添加调试日志
  2792. // 使用延时确保DOM完全更新
  2793. setTimeout(() => {
  2794. debouncedCalculate();
  2795. }, 300);
  2796. }
  2797. });
  2798.  
  2799. // 观察整个结果容器及其子元素
  2800. const resultsContainer = document.querySelector('.results-container');
  2801. if (resultsContainer) {
  2802. resultsObserver.observe(resultsContainer, {
  2803. childList: true,
  2804. subtree: true,
  2805. attributes: true,
  2806. characterData: true
  2807. });
  2808.  
  2809. // 同时观察父元素,以防结果容器被替换
  2810. if (resultsContainer.parentNode) {
  2811. resultsObserver.observe(resultsContainer.parentNode, {
  2812. childList: true
  2813. });
  2814. }
  2815. }
  2816.  
  2817. // 添加滚动事件监听器
  2818. window.addEventListener('scroll', () => {
  2819. // 检查是否滚动到底部附近
  2820. if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight - 1000) {
  2821. debouncedCalculate();
  2822. }
  2823. });
  2824.  
  2825. // 将函数添加到window对象,以便其他地方可以调用
  2826. window.calculateDPS = calculateDPS;
  2827. window.calculateShield = calculateShield;
  2828. window.calculateEvasion = calculateEvasion;
  2829. window.calculateArmour = calculateArmour;
  2830. window.calculateDefence = calculateDefence;
  2831. window.debouncedCalculateDPS = debouncedCalculateDPS;
  2832. window.debouncedCalculateShield = debouncedCalculateShield;
  2833. window.debouncedCalculateEvasion = debouncedCalculateEvasion;
  2834. window.debouncedCalculateArmour = debouncedCalculateArmour;
  2835. window.debouncedCalculateDefence = debouncedCalculateDefence;
  2836. window.debouncedCalculate = debouncedCalculate;
  2837.  
  2838. // 监听URL变化
  2839. let lastUrl = location.href;
  2840. const urlCheckInterval = setInterval(() => {
  2841. const currentUrl = location.href;
  2842. if ((currentUrl !== lastUrl || currentUrl.includes('pathofexile.com/trade2/search/poe2')) && STATE.autoLoadEnabled) {
  2843. lastUrl = currentUrl;
  2844. setTimeout(() => {
  2845. if (isSearchResultPage()) {
  2846. autoLoadAllResults();
  2847. }
  2848. }, 100);
  2849. }
  2850. }, 100);
  2851. // 初始检查
  2852. setTimeout(() => {
  2853. if (isSearchResultPage() && STATE.autoLoadEnabled) {
  2854. autoLoadAllResults();
  2855. }
  2856. }, 100);
  2857. // 延迟初始计算,确保页面元素已加载
  2858. setTimeout(() => {
  2859. // 初始计算DPS和护盾
  2860. calculateDPS();
  2861. calculateShield();
  2862. calculateEvasion();
  2863. calculateArmour();
  2864. calculateDefence();
  2865.  
  2866. // 如果没有找到物品,设置重试
  2867. const retryCalculation = () => {
  2868. const items = document.querySelectorAll('.row[data-id]');
  2869. if (items.length > 0) {
  2870. console.log('重试计算,找到物品数量:', items.length);
  2871. calculateDPS();
  2872. calculateShield();
  2873. calculateEvasion();
  2874. calculateArmour();
  2875. calculateDefence();
  2876. } else {
  2877. console.log('未找到物品,500ms后重试...');
  2878. setTimeout(retryCalculation, 500);
  2879. }
  2880. };
  2881.  
  2882. // 如果初始计算没有找到物品,启动重试机制
  2883. const items = document.querySelectorAll('.row[data-id]');
  2884. if (items.length === 0) {
  2885. console.log('初始计算未找到物品,启动重试机制');
  2886. setTimeout(retryCalculation, 500);
  2887. }
  2888. }, 500);
  2889.  
  2890. } catch (error) {
  2891. console.error('初始化时出错:', error);
  2892. }
  2893. }
  2894. // 修改 updateConfig 函数
  2895. function updateConfig(category, name) {
  2896. if (confirm(`确定要用当前页面更新配置 "${name}" 吗?`)) {
  2897. STATE.configs[category][name] = {
  2898. url: window.location.href
  2899. };
  2900. GM_setValue('savedConfigs', STATE.configs);
  2901. updateConfigList();
  2902. }
  2903. }
  2904. // 添加预设关键词相关函数
  2905. function saveSearchPreset() {
  2906. const nameInput = document.getElementById('preset-name');
  2907. const keywordsInput = document.getElementById('preset-keywords');
  2908. const name = nameInput.value.trim();
  2909. const keywords = keywordsInput.value.trim();
  2910. const saveBtn = document.getElementById('save-preset');
  2911.  
  2912. if (!name || !keywords) {
  2913. alert('请输入预设名称和关键词');
  2914. return;
  2915. }
  2916.  
  2917. // 检查是否在编辑模式
  2918. if (nameInput.dataset.editMode === 'true') {
  2919. const originalName = nameInput.dataset.originalName;
  2920. // 如果名称改变了,删除旧的预设
  2921. if (originalName !== name) {
  2922. delete STATE.searchPresets[originalName];
  2923. }
  2924. // 清除编辑模式标记
  2925. delete nameInput.dataset.editMode;
  2926. delete nameInput.dataset.originalName;
  2927. saveBtn.textContent = '保存预设';
  2928. } else if (STATE.searchPresets[name] && !confirm(`预设 "${name}" 已存在,是否覆盖?`)) {
  2929. return;
  2930. }
  2931.  
  2932. STATE.searchPresets[name] = keywords;
  2933. GM_setValue('searchPresets', STATE.searchPresets);
  2934. updatePresetList();
  2935.  
  2936. nameInput.value = '';
  2937. keywordsInput.value = '';
  2938. }
  2939.  
  2940. function updatePresetList() {
  2941. const presetList = document.getElementById('preset-list');
  2942. presetList.innerHTML = '';
  2943.  
  2944. Object.entries(STATE.searchPresets).forEach(([name, keywords]) => {
  2945. const presetItem = document.createElement('div');
  2946. presetItem.style.cssText = `
  2947. display: grid;
  2948. grid-template-columns: 1fr auto auto;
  2949. align-items: center;
  2950. padding: 8px;
  2951. margin: 5px 0;
  2952. background: #3d3d3d;
  2953. border-radius: 4px;
  2954. gap: 10px;
  2955. `;
  2956.  
  2957. const nameSpan = document.createElement('span');
  2958. nameSpan.textContent = name; // 只显示预设名称
  2959. nameSpan.title = keywords; // 将关键词设置为提示文本
  2960. nameSpan.style.cssText = `
  2961. overflow: hidden;
  2962. text-overflow: ellipsis;
  2963. white-space: nowrap;
  2964. cursor: help; // 添加提示光标
  2965. `;
  2966.  
  2967. const editBtn = document.createElement('button');
  2968. editBtn.textContent = '编辑';
  2969. editBtn.style.cssText = `
  2970. background: #27ae60;
  2971. border: none;
  2972. color: #fff;
  2973. padding: 3px 12px;
  2974. cursor: pointer;
  2975. border-radius: 3px;
  2976. `;
  2977. editBtn.onclick = () => {
  2978. const presetEditModal = document.getElementById('preset-edit-modal');
  2979. const presetEditOverlay = document.getElementById('preset-edit-overlay');
  2980. const presetEditTitle = document.getElementById('preset-edit-title');
  2981. const nameInput = document.getElementById('preset-name');
  2982. const keywordsInput = document.getElementById('preset-keywords');
  2983.  
  2984. presetEditTitle.textContent = '编辑预设';
  2985. nameInput.value = name;
  2986. keywordsInput.value = keywords;
  2987. nameInput.dataset.editMode = 'true';
  2988. nameInput.dataset.originalName = name;
  2989.  
  2990. presetEditModal.style.display = 'block';
  2991. presetEditOverlay.style.display = 'block';
  2992. };
  2993.  
  2994. const deleteBtn = document.createElement('button');
  2995. deleteBtn.textContent = '删除';
  2996. deleteBtn.style.cssText = `
  2997. background: #e74c3c;
  2998. border: none;
  2999. color: #fff;
  3000. padding: 3px 12px;
  3001. cursor: pointer;
  3002. border-radius: 3px;
  3003. `;
  3004. deleteBtn.onclick = () => {
  3005. if (confirm(`确定要删除预设 "${name}" 吗?`)) {
  3006. delete STATE.searchPresets[name];
  3007. GM_setValue('searchPresets', STATE.searchPresets);
  3008. updatePresetList();
  3009. }
  3010. };
  3011.  
  3012. presetItem.appendChild(nameSpan);
  3013. presetItem.appendChild(editBtn);
  3014. presetItem.appendChild(deleteBtn);
  3015. presetList.appendChild(presetItem);
  3016. });
  3017. }
  3018. setTimeout(init, 2000);
  3019.  
  3020. // 在clearHighlights函数后添加
  3021. function updateCardVisibility() {
  3022. const allCards = document.querySelectorAll('.row[data-id]');
  3023. if (STATE.showOnlyMatched) {
  3024. // 如果启用了"只显示匹配项",隐藏所有非匹配卡片
  3025. allCards.forEach(card => {
  3026. if (STATE.matchedCards.includes(card)) {
  3027. card.style.display = '';
  3028. } else {
  3029. card.style.display = 'none';
  3030. }
  3031. });
  3032. } else {
  3033. // 如果禁用了"只显示匹配项",显示所有卡片
  3034. allCards.forEach(card => {
  3035. card.style.display = '';
  3036. });
  3037. }
  3038. }
  3039.  
  3040. // 计算护盾值
  3041. function calculateShield() {
  3042. console.log('Calculating Shield...');
  3043. const items = document.querySelectorAll('.row[data-id]');
  3044. console.log('Found items:', items.length);
  3045. let processedCount = 0;
  3046. let successCount = 0;
  3047.  
  3048. items.forEach((item, index) => {
  3049. try {
  3050. // 如果已经计算过护盾值,则跳过
  3051. if (item.querySelector('.shield-display')) {
  3052. processedCount++;
  3053. return;
  3054. }
  3055.  
  3056. // 1. 获取符文孔数量
  3057. const socketsDiv = item.querySelector('.sockets');
  3058. if (!socketsDiv) {
  3059. console.debug(`Item ${index}: No sockets div found`);
  3060. return;
  3061. }
  3062. // 修改符文孔数量的获取逻辑
  3063. let numSockets = 0;
  3064. for (let i = 1; i <= 6; i++) { // 假设最多6个符文孔
  3065. if (socketsDiv.classList.contains(`numSockets${i}`)) {
  3066. numSockets = i;
  3067. break;
  3068. }
  3069. }
  3070. if (numSockets === 0) {
  3071. // 尝试其他方式获取符文孔数量
  3072. const socketText = socketsDiv.textContent.trim();
  3073. if (socketText) {
  3074. numSockets = socketText.length;
  3075. } else {
  3076. numSockets = 1; // 默认值
  3077. }
  3078. console.debug(`Item ${index}: Using alternative socket count: ${numSockets}`);
  3079. }
  3080.  
  3081. // 2. 获取能量护盾值
  3082. const esSpan = item.querySelector('span[data-field="es"]');
  3083. if (!esSpan) {
  3084. console.debug(`Item ${index}: No ES span found`);
  3085. return;
  3086. }
  3087.  
  3088. const esValue = esSpan.querySelector('.colourAugmented, .colourDefault');
  3089. if (!esValue) {
  3090. console.debug(`Item ${index}: No ES value found`);
  3091. return;
  3092. }
  3093. const totalES = parseInt(esValue.textContent);
  3094. if (isNaN(totalES)) {
  3095. console.debug(`Item ${index}: Invalid ES value: ${esValue.textContent}`);
  3096. return;
  3097. }
  3098.  
  3099. // 3. 获取当前品质
  3100. let currentQuality = 0;
  3101. const qualitySpan = item.querySelector('span[data-field="quality"]');
  3102. if (qualitySpan) {
  3103. const qualityMatch = qualitySpan.textContent.match(/\+(\d+)%/);
  3104. if (qualityMatch) {
  3105. currentQuality = parseInt(qualityMatch[1]);
  3106. }
  3107. }
  3108. // 计算品质差值
  3109. const qualityDiff = 20 - currentQuality;
  3110.  
  3111. // 4. 获取护盾增加百分比
  3112. let esInc = 0;
  3113.  
  3114. // 定义可能包含护盾增加的词缀ID列表
  3115. const possibleESIncIds = [
  3116. 'stat.explicit.stat_4015621042', // 原有ID
  3117. 'stat.explicit.stat_1999113824', // 增加闪避与能量护盾
  3118. 'stat.explicit.stat_2866361420', // 增加能量护盾
  3119. 'stat.explicit.stat_2511217560', // 增加最大能量护盾
  3120. 'stat.explicit.stat_3489782002', // 增加能量护盾和魔力
  3121. 'stat.explicit.stat_3321629045' // 增加护甲与能量护盾
  3122. ];
  3123.  
  3124. // 遍历所有可能的词缀ID
  3125. for (const statId of possibleESIncIds) {
  3126. const statSpan = item.querySelector(`span[data-field="${statId}"]`);
  3127. if (statSpan) {
  3128. // 检查词缀文本是否包含"能量护盾"或"Energy Shield"
  3129. const statText = statSpan.textContent;
  3130. if (statText.includes('能量护盾') || statText.includes('Energy Shield')) {
  3131. // 提取百分比数值
  3132. const incMatch = statText.match(/(\d+)%/);
  3133. if (incMatch) {
  3134. esInc += parseInt(incMatch[1]);
  3135. console.debug(`Found ES increase: ${incMatch[1]}% from stat ${statId}`);
  3136. }
  3137. }
  3138. }
  3139. }
  3140.  
  3141. // 通用方法:查找所有包含"能量护盾"或"Energy Shield"的显式词缀
  3142. const allExplicitStats = item.querySelectorAll('span[data-field^="stat.explicit.stat_"]');
  3143. allExplicitStats.forEach(statSpan => {
  3144. // 检查是否已经在上面的特定ID列表中处理过
  3145. const statId = statSpan.getAttribute('data-field');
  3146. if (!possibleESIncIds.includes(statId)) {
  3147. const statText = statSpan.textContent;
  3148. // 检查是否包含能量护盾相关文本和百分比增加
  3149. if ((statText.includes('能量护盾') || statText.includes('Energy Shield')) &&
  3150. statText.includes('%') &&
  3151. (statText.includes('增加') || statText.includes('increased'))) {
  3152. const incMatch = statText.match(/(\d+)%/);
  3153. if (incMatch) {
  3154. esInc += parseInt(incMatch[1]);
  3155. console.debug(`Found additional ES increase: ${incMatch[1]}% from stat ${statId}`);
  3156. // 将新发现的ID添加到列表中,以便将来使用
  3157. possibleESIncIds.push(statId);
  3158. }
  3159. }
  3160. }
  3161. });
  3162.  
  3163. // 5. 获取已插入的符文提供的增益和数量
  3164. let insertedRuneBonus = 0;
  3165. let insertedRuneCount = 0;
  3166. let esRuneInc = 0; // 新增:专门用于记录护盾符文的增益
  3167.  
  3168. // 获取符文增益 - 能量护盾增加
  3169. const esRuneStatSpan = item.querySelector('span[data-field="stat.rune.stat_3523867985"]');
  3170. if (esRuneStatSpan) {
  3171. const runeMatch = esRuneStatSpan.textContent.match(/(\d+)%/);
  3172. if (runeMatch) {
  3173. insertedRuneBonus = parseInt(runeMatch[1]);
  3174. insertedRuneCount = insertedRuneBonus / 20;
  3175. }
  3176. }
  3177.  
  3178. // 获取符文增益 - 能量护盾百分比增加
  3179. const esIncRuneStatSpan = item.querySelector('span[data-field="stat.rune.stat_2866361420"]');
  3180. if (esIncRuneStatSpan) {
  3181. const esIncRuneMatch = esIncRuneStatSpan.textContent.match(/(\d+)%/);
  3182. if (esIncRuneMatch) {
  3183. esRuneInc = parseInt(esIncRuneMatch[1]);
  3184. }
  3185. }
  3186.  
  3187. // 6. 计算可用的符文孔位增益
  3188. let availableRuneBonus = 0;
  3189. // 计算剩余可用的孔数
  3190. const remainingSlots = numSockets - insertedRuneCount;
  3191. availableRuneBonus = remainingSlots * 20;
  3192.  
  3193. // 7. 计算基础护盾
  3194. // 当前护盾 = 基础护盾 * (1 + 当前品质/100) * (1 + (已插入符文增益 + 装备增益 + 护盾符文增益)/100)
  3195. const baseES = Math.round(totalES / (1 + currentQuality/100) / (1 + (insertedRuneBonus + esInc + esRuneInc)/100));
  3196.  
  3197. // 8. 计算最大可能护盾(考虑品质提升和剩余符文孔)
  3198. // 最大护盾 = 基础护盾 * (1 + (当前品质 + 品质差值)/100) * (1 + (装备增益 + 已插入符文增益 + 可用符文增益)/100)
  3199. const maxES = Math.round(baseES * (1 + (currentQuality + qualityDiff)/100) * (1 + (esInc + insertedRuneBonus + availableRuneBonus)/100));
  3200.  
  3201. // 创建护盾显示元素
  3202. const shieldDisplay = document.createElement('span');
  3203. shieldDisplay.className = 'shield-display';
  3204. shieldDisplay.style.cssText = `
  3205. color: #6495ED;
  3206. font-weight: bold;
  3207. margin-left: 10px;
  3208. `;
  3209. shieldDisplay.textContent = `总护盾: ${maxES}`;
  3210.  
  3211. // 将护盾显示添加到能量护盾后面
  3212. esSpan.appendChild(shieldDisplay);
  3213.  
  3214. successCount++;
  3215. processedCount++;
  3216.  
  3217. // 添加调试信息
  3218. console.debug(`Item ${index} Shield calculation:`, {
  3219. totalES,
  3220. currentQuality,
  3221. qualityDiff,
  3222. esInc,
  3223. insertedRuneBonus,
  3224. insertedRuneCount,
  3225. numSockets,
  3226. availableRuneBonus,
  3227. baseES,
  3228. maxES
  3229. });
  3230. } catch (error) {
  3231. console.error(`Error calculating shield for item ${index}:`, error);
  3232. }
  3233. });
  3234.  
  3235. console.log(`Shield calculation completed: ${successCount} successful, ${processedCount} processed out of ${items.length} items`);
  3236.  
  3237. // 更新护盾排序面板
  3238. updateShieldPanel();
  3239. }
  3240.  
  3241. // 创建护盾排序面板
  3242. function createShieldPanel() {
  3243. const panel = document.createElement('div');
  3244. panel.id = 'shield-sort-panel';
  3245. panel.style.cssText = `
  3246. position: fixed;
  3247. right: 20px;
  3248. top: 50%;
  3249. transform: translateY(calc(-50% + 200px));
  3250. background: rgba(28, 28, 28, 0.95);
  3251. padding: 8px;
  3252. border-radius: 6px;
  3253. box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
  3254. border: 1px solid #444;
  3255. width: 180px;
  3256. max-height: 60vh;
  3257. z-index: 9997;
  3258. display: none;
  3259. `;
  3260.  
  3261. // 添加标题
  3262. const title = document.createElement('div');
  3263. title.style.cssText = `
  3264. font-weight: bold;
  3265. color: #8acdff;
  3266. margin-bottom: 6px;
  3267. padding-bottom: 3px;
  3268. border-bottom: 1px solid #444;
  3269. display: flex;
  3270. justify-content: space-between;
  3271. align-items: center;
  3272. cursor: pointer;
  3273. font-size: 14px;
  3274. user-select: none;
  3275. height: 18px;
  3276. `;
  3277. // 添加展开/收起指示器
  3278. const indicator = document.createElement('span');
  3279. indicator.textContent = '▼';
  3280. indicator.style.marginRight = '3px';
  3281. indicator.style.fontSize = '10px';
  3282. indicator.id = 'integrated-panel-indicator';
  3283. const titleText = document.createElement('span');
  3284. titleText.textContent = 'ES';
  3285. const titleLeft = document.createElement('div');
  3286. titleLeft.style.display = 'flex';
  3287. titleLeft.style.alignItems = 'center';
  3288. titleLeft.appendChild(indicator);
  3289. titleLeft.appendChild(titleText);
  3290.  
  3291. title.appendChild(titleLeft);
  3292.  
  3293. // 添加关闭按钮
  3294. const closeBtn = document.createElement('button');
  3295. closeBtn.textContent = '×';
  3296. closeBtn.style.cssText = `
  3297. background: none;
  3298. border: none;
  3299. color: #999;
  3300. font-size: 20px;
  3301. cursor: pointer;
  3302. padding: 0 5px;
  3303. `;
  3304. closeBtn.onclick = (e) => {
  3305. e.stopPropagation();
  3306. panel.style.display = 'none';
  3307. };
  3308. title.appendChild(closeBtn);
  3309.  
  3310. // 添加内容容器
  3311. const content = document.createElement('div');
  3312. content.id = 'shield-sort-content';
  3313. content.style.cssText = `
  3314. max-height: calc(60vh - 35px);
  3315. overflow-y: auto;
  3316. transition: max-height 0.3s ease-out;
  3317. padding-right: 2px;
  3318. `;
  3319.  
  3320. // 添加展开/收起功能
  3321. let isExpanded = true;
  3322. title.onclick = () => {
  3323. isExpanded = !isExpanded;
  3324. content.style.maxHeight = isExpanded ? 'calc(60vh - 35px)' : '0';
  3325. content.style.overflow = isExpanded ? 'auto' : 'hidden';
  3326. indicator.textContent = isExpanded ? '▼' : '▶';
  3327. };
  3328.  
  3329. // 添加组件到面板
  3330. panel.appendChild(title);
  3331. panel.appendChild(content);
  3332.  
  3333. // 添加到文档
  3334. document.body.appendChild(panel);
  3335.  
  3336. console.log('Shield panel created successfully');
  3337. return panel;
  3338. }
  3339.  
  3340. // 更新护盾排序面板
  3341. function updateShieldPanel() {
  3342. try {
  3343. console.log('Updating shield panel...');
  3344.  
  3345. // 获取或创建面板
  3346. const panel = document.getElementById('integrated-sort-panel') || createIntegratedPanel();
  3347. const content = document.getElementById('shield-sort-content');
  3348. if (!content) {
  3349. console.error('Shield sort content not found');
  3350. return;
  3351. }
  3352.  
  3353. // 清空现有内容
  3354. content.innerHTML = '';
  3355.  
  3356. // 清除可能存在的旧数据属性
  3357. content.removeAttribute('data-armour-content');
  3358. content.removeAttribute('data-evasion-content');
  3359. content.removeAttribute('data-dps-content');
  3360. content.removeAttribute('data-defence-content');
  3361.  
  3362. // 获取所有物品
  3363. const items = document.querySelectorAll('.row[data-id]');
  3364. const shieldData = [];
  3365.  
  3366. // 收集护盾数据
  3367. items.forEach(item => {
  3368. const shieldDisplay = item.querySelector('.shield-display');
  3369. if (shieldDisplay) {
  3370. const shield = parseInt(shieldDisplay.textContent.replace('总护盾: ', ''));
  3371. // 获取价格
  3372. const priceElement = item.querySelector('.price [data-field="price"]');
  3373. let price = '未标价';
  3374. if (priceElement) {
  3375. const amount = priceElement.querySelector('span:not(.price-label):not(.currency-text)');
  3376. const currencyText = priceElement.querySelector('.currency-text');
  3377. if (amount && currencyText) {
  3378. const amountText = amount.textContent.trim();
  3379. const currency = currencyText.querySelector('span')?.textContent || currencyText.textContent;
  3380. // 使用全局的convertCurrencyText函数
  3381. const simpleCurrency = convertCurrencyText(currency);
  3382. price = `${amountText}${simpleCurrency}`;
  3383. }
  3384. }
  3385.  
  3386. shieldData.push({
  3387. shield,
  3388. price,
  3389. element: item
  3390. });
  3391. }
  3392. });
  3393.  
  3394. console.log(`Found ${shieldData.length} items with shield data`);
  3395.  
  3396. // 如果没有护盾数据,隐藏护盾选项卡
  3397. const shieldTab = panel.querySelector('[data-tab="shield"]');
  3398. if (shieldData.length === 0) {
  3399. if (shieldTab) shieldTab.style.display = 'none';
  3400.  
  3401. // 如果DPS选项卡也是隐藏的,则隐藏整个面板
  3402. const dpsTab = panel.querySelector('[data-tab="dps"]');
  3403. const evasionTab = panel.querySelector('[data-tab="evasion"]');
  3404. const armourTab = panel.querySelector('[data-tab="armour"]');
  3405. const defenceTab = panel.querySelector('[data-tab="defence"]');
  3406. // 检查是否所有选项卡都隐藏了
  3407. if ((dpsTab && dpsTab.style.display === 'none') &&
  3408. (evasionTab && evasionTab.style.display === 'none') &&
  3409. (armourTab && armourTab.style.display === 'none') &&
  3410. (defenceTab && defenceTab.style.display === 'none')) {
  3411. panel.style.display = 'none';
  3412. }
  3413. // 移除自动切换到护盾选项卡的代码,保留当前选项卡状态
  3414. return;
  3415. } else {
  3416. // 有护盾数据,确保护盾选项卡可见
  3417. if (shieldTab) shieldTab.style.display = '';
  3418. }
  3419.  
  3420. // 按护盾值从高到低排序
  3421. shieldData.sort((a, b) => b.shield - a.shield);
  3422.  
  3423. // 更新面板内容
  3424. shieldData.forEach(({shield, price, element}) => {
  3425. const shieldItem = document.createElement('div');
  3426. shieldItem.className = 'dps-item'; // 复用DPS项的样式
  3427.  
  3428. // 创建护盾显示
  3429. const shieldText = document.createElement('span');
  3430. shieldText.className = 'shield-value'; // 使用护盾专用的样式
  3431. shieldText.textContent = shield.toString();
  3432.  
  3433. // 创建价格显示
  3434. const priceText = document.createElement('span');
  3435. priceText.className = 'price-value';
  3436. priceText.textContent = price;
  3437.  
  3438. // 添加到护盾项
  3439. shieldItem.appendChild(shieldText);
  3440. shieldItem.appendChild(priceText);
  3441.  
  3442. // 添加点击事件
  3443. shieldItem.onclick = () => {
  3444. element.scrollIntoView({ behavior: 'smooth', block: 'center' });
  3445. // 添加高亮效果
  3446. element.style.transition = 'background-color 0.3s';
  3447. element.style.backgroundColor = 'rgba(138, 205, 255, 0.2)';
  3448. setTimeout(() => {
  3449. element.style.backgroundColor = '';
  3450. }, 1500);
  3451. };
  3452.  
  3453. content.appendChild(shieldItem);
  3454. });
  3455.  
  3456. // 显示面板
  3457. panel.style.display = 'block';
  3458. console.log('Shield panel updated successfully');
  3459. } catch (error) {
  3460. console.error('Error updating shield panel:', error);
  3461. }
  3462. }
  3463.  
  3464. // 辅助函数:安全地查询选择器
  3465. function safeQuerySelector(element, selector) {
  3466. try {
  3467. return element.querySelector(selector);
  3468. } catch (error) {
  3469. console.error(`Error querying selector "${selector}":`, error);
  3470. return null;
  3471. }
  3472. }
  3473.  
  3474. // 创建整合面板,包含总防御、DPS、护盾、闪避和护甲五个选项卡
  3475. function createIntegratedPanel() {
  3476. // 检查是否已存在面板
  3477. let panel = document.getElementById('integrated-sort-panel');
  3478. if (panel) return panel;
  3479. panel = document.createElement('div');
  3480. panel.id = 'integrated-sort-panel';
  3481. panel.style.cssText = `
  3482. position: fixed;
  3483. right: 20px;
  3484. top: 50%;
  3485. transform: translateY(-50%);
  3486. background: rgba(28, 28, 28, 0.95);
  3487. padding: 8px;
  3488. border-radius: 6px;
  3489. box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
  3490. border: 1px solid #444;
  3491. width: 180px;
  3492. max-height: 60vh;
  3493. z-index: 9997;
  3494. display: none;
  3495. `;
  3496.  
  3497. // 创建标题栏
  3498. const titleBar = document.createElement('div');
  3499. titleBar.style.cssText = `
  3500. display: flex;
  3501. justify-content: space-between;
  3502. align-items: center;
  3503. margin-bottom: 6px;
  3504. padding-bottom: 3px;
  3505. border-bottom: 1px solid #444;
  3506. font-size: 13px;
  3507. user-select: none;
  3508. height: 18px;
  3509. `;
  3510. // 创建选项卡容器
  3511. const tabsContainer = document.createElement('div');
  3512. tabsContainer.style.cssText = `
  3513. display: flex;
  3514. gap: 4px;
  3515. flex-wrap: nowrap;
  3516. `;
  3517. // 创建总防御选项卡(默认激活)
  3518. const defenceTab = document.createElement('div');
  3519. defenceTab.textContent = 'DEF';
  3520. defenceTab.className = 'sort-tab active';
  3521. defenceTab.dataset.tab = 'defence';
  3522. defenceTab.style.cssText = `
  3523. color: #9370DB;
  3524. font-weight: bold;
  3525. cursor: pointer;
  3526. padding: 0 3px;
  3527. font-size: 12px;
  3528. `;
  3529. // 创建DPS选项卡
  3530. const dpsTab = document.createElement('div');
  3531. dpsTab.textContent = 'DPS';
  3532. dpsTab.className = 'sort-tab';
  3533. dpsTab.dataset.tab = 'dps';
  3534. dpsTab.style.cssText = `
  3535. color: #FFD700;
  3536. font-weight: bold;
  3537. cursor: pointer;
  3538. padding: 0 3px;
  3539. opacity: 0.6;
  3540. font-size: 12px;
  3541. `;
  3542. // 创建护盾选项卡
  3543. const shieldTab = document.createElement('div');
  3544. shieldTab.textContent = 'ES';
  3545. shieldTab.className = 'sort-tab';
  3546. shieldTab.dataset.tab = 'shield';
  3547. shieldTab.style.cssText = `
  3548. color: #8acdff;
  3549. font-weight: bold;
  3550. cursor: pointer;
  3551. padding: 0 3px;
  3552. opacity: 0.6;
  3553. font-size: 12px;
  3554. `;
  3555. // 创建闪避选项卡
  3556. const evasionTab = document.createElement('div');
  3557. evasionTab.textContent = 'EV';
  3558. evasionTab.className = 'sort-tab';
  3559. evasionTab.dataset.tab = 'evasion';
  3560. evasionTab.style.cssText = `
  3561. color: #7FFF00;
  3562. font-weight: bold;
  3563. cursor: pointer;
  3564. padding: 0 3px;
  3565. opacity: 0.6;
  3566. font-size: 12px;
  3567. `;
  3568. // 创建护甲选项卡
  3569. const armourTab = document.createElement('div');
  3570. armourTab.textContent = 'AR';
  3571. armourTab.className = 'sort-tab';
  3572. armourTab.dataset.tab = 'armour';
  3573. armourTab.style.cssText = `
  3574. color: #FF6347;
  3575. font-weight: bold;
  3576. cursor: pointer;
  3577. padding: 0 3px;
  3578. opacity: 0.6;
  3579. font-size: 12px;
  3580. `;
  3581. // 添加选项卡到容器
  3582. tabsContainer.appendChild(defenceTab);
  3583. tabsContainer.appendChild(dpsTab);
  3584. tabsContainer.appendChild(shieldTab);
  3585. tabsContainer.appendChild(evasionTab);
  3586. tabsContainer.appendChild(armourTab);
  3587. // 创建展开/收起指示器
  3588. const indicator = document.createElement('span');
  3589. indicator.textContent = '▼';
  3590. indicator.style.marginRight = '3px';
  3591. indicator.style.fontSize = '10px';
  3592. indicator.id = 'integrated-panel-indicator';
  3593. // 创建左侧容器
  3594. const titleLeft = document.createElement('div');
  3595. titleLeft.style.cssText = `
  3596. display: flex;
  3597. align-items: center;
  3598. `;
  3599. titleLeft.appendChild(indicator);
  3600. titleLeft.appendChild(tabsContainer);
  3601. // 添加关闭按钮
  3602. const closeBtn = document.createElement('button');
  3603. closeBtn.textContent = '×';
  3604. closeBtn.style.cssText = `
  3605. background: none;
  3606. border: none;
  3607. color: #999;
  3608. font-size: 20px;
  3609. cursor: pointer;
  3610. padding: 0 5px;
  3611. `;
  3612. closeBtn.onclick = (e) => {
  3613. e.stopPropagation();
  3614. panel.style.display = 'none';
  3615. };
  3616. // 组装标题栏
  3617. titleBar.appendChild(titleLeft);
  3618. titleBar.appendChild(closeBtn);
  3619. // 创建内容容器
  3620. const contentContainer = document.createElement('div');
  3621. contentContainer.style.cssText = `
  3622. position: relative;
  3623. `;
  3624. // 创建总防御内容
  3625. const defenceContent = document.createElement('div');
  3626. defenceContent.id = 'defence-sort-content';
  3627. defenceContent.className = 'sort-content active';
  3628. defenceContent.style.cssText = `
  3629. max-height: calc(60vh - 35px);
  3630. overflow-y: auto;
  3631. transition: max-height 0.3s ease-out, opacity 0.3s ease-out;
  3632. padding-right: 2px;
  3633. `;
  3634. // 创建DPS内容
  3635. const dpsContent = document.createElement('div');
  3636. dpsContent.id = 'dps-sort-content';
  3637. dpsContent.className = 'sort-content';
  3638. dpsContent.style.cssText = `
  3639. max-height: calc(60vh - 35px);
  3640. overflow-y: auto;
  3641. transition: max-height 0.3s ease-out, opacity 0.3s ease-out;
  3642. padding-right: 2px;
  3643. display: none;
  3644. `;
  3645. // 创建护盾内容
  3646. const shieldContent = document.createElement('div');
  3647. shieldContent.id = 'shield-sort-content';
  3648. shieldContent.className = 'sort-content';
  3649. shieldContent.style.cssText = `
  3650. max-height: calc(60vh - 35px);
  3651. overflow-y: auto;
  3652. transition: max-height 0.3s ease-out, opacity 0.3s ease-out;
  3653. padding-right: 2px;
  3654. display: none;
  3655. `;
  3656. // 创建闪避内容
  3657. const evasionContent = document.createElement('div');
  3658. evasionContent.id = 'evasion-sort-content';
  3659. evasionContent.className = 'sort-content';
  3660. evasionContent.style.cssText = `
  3661. max-height: calc(60vh - 35px);
  3662. overflow-y: auto;
  3663. transition: max-height 0.3s ease-out, opacity 0.3s ease-out;
  3664. padding-right: 2px;
  3665. display: none;
  3666. `;
  3667. // 创建护甲内容
  3668. const armourContent = document.createElement('div');
  3669. armourContent.id = 'armour-sort-content';
  3670. armourContent.className = 'sort-content';
  3671. armourContent.style.cssText = `
  3672. max-height: calc(60vh - 35px);
  3673. overflow-y: auto;
  3674. transition: max-height 0.3s ease-out, opacity 0.3s ease-out;
  3675. padding-right: 2px;
  3676. display: none;
  3677. `;
  3678. // 添加内容到容器
  3679. contentContainer.appendChild(defenceContent);
  3680. contentContainer.appendChild(dpsContent);
  3681. contentContainer.appendChild(shieldContent);
  3682. contentContainer.appendChild(evasionContent);
  3683. contentContainer.appendChild(armourContent);
  3684. // 添加展开/收起功能
  3685. let isExpanded = true;
  3686. indicator.onclick = (e) => {
  3687. e.stopPropagation();
  3688. isExpanded = !isExpanded;
  3689. defenceContent.style.maxHeight = isExpanded ? 'calc(60vh - 35px)' : '0';
  3690. dpsContent.style.maxHeight = isExpanded ? 'calc(60vh - 35px)' : '0';
  3691. shieldContent.style.maxHeight = isExpanded ? 'calc(60vh - 35px)' : '0';
  3692. evasionContent.style.maxHeight = isExpanded ? 'calc(60vh - 35px)' : '0';
  3693. armourContent.style.maxHeight = isExpanded ? 'calc(60vh - 35px)' : '0';
  3694. defenceContent.style.overflow = isExpanded ? 'auto' : 'hidden';
  3695. dpsContent.style.overflow = isExpanded ? 'auto' : 'hidden';
  3696. shieldContent.style.overflow = isExpanded ? 'auto' : 'hidden';
  3697. evasionContent.style.overflow = isExpanded ? 'auto' : 'hidden';
  3698. armourContent.style.overflow = isExpanded ? 'auto' : 'hidden';
  3699. indicator.textContent = isExpanded ? '▼' : '▶';
  3700. };
  3701.  
  3702. // 添加选项卡切换功能
  3703. defenceTab.onclick = () => {
  3704. defenceTab.classList.add('active');
  3705. dpsTab.classList.remove('active');
  3706. shieldTab.classList.remove('active');
  3707. evasionTab.classList.remove('active');
  3708. armourTab.classList.remove('active');
  3709. defenceContent.style.display = 'block';
  3710. dpsContent.style.display = 'none';
  3711. shieldContent.style.display = 'none';
  3712. evasionContent.style.display = 'none';
  3713. armourContent.style.display = 'none';
  3714. defenceTab.style.opacity = '1';
  3715. dpsTab.style.opacity = '0.6';
  3716. shieldTab.style.opacity = '0.6';
  3717. evasionTab.style.opacity = '0.6';
  3718. armourTab.style.opacity = '0.6';
  3719. };
  3720. dpsTab.onclick = () => {
  3721. dpsTab.classList.add('active');
  3722. defenceTab.classList.remove('active');
  3723. shieldTab.classList.remove('active');
  3724. evasionTab.classList.remove('active');
  3725. armourTab.classList.remove('active');
  3726. dpsContent.style.display = 'block';
  3727. defenceContent.style.display = 'none';
  3728. shieldContent.style.display = 'none';
  3729. evasionContent.style.display = 'none';
  3730. armourContent.style.display = 'none';
  3731. dpsTab.style.opacity = '1';
  3732. defenceTab.style.opacity = '0.6';
  3733. shieldTab.style.opacity = '0.6';
  3734. evasionTab.style.opacity = '0.6';
  3735. armourTab.style.opacity = '0.6';
  3736. };
  3737. shieldTab.onclick = () => {
  3738. shieldTab.classList.add('active');
  3739. defenceTab.classList.remove('active');
  3740. dpsTab.classList.remove('active');
  3741. evasionTab.classList.remove('active');
  3742. armourTab.classList.remove('active');
  3743. shieldContent.style.display = 'block';
  3744. defenceContent.style.display = 'none';
  3745. dpsContent.style.display = 'none';
  3746. evasionContent.style.display = 'none';
  3747. armourContent.style.display = 'none';
  3748. shieldTab.style.opacity = '1';
  3749. defenceTab.style.opacity = '0.6';
  3750. dpsTab.style.opacity = '0.6';
  3751. evasionTab.style.opacity = '0.6';
  3752. armourTab.style.opacity = '0.6';
  3753. };
  3754. evasionTab.onclick = () => {
  3755. evasionTab.classList.add('active');
  3756. defenceTab.classList.remove('active');
  3757. dpsTab.classList.remove('active');
  3758. shieldTab.classList.remove('active');
  3759. armourTab.classList.remove('active');
  3760. evasionContent.style.display = 'block';
  3761. defenceContent.style.display = 'none';
  3762. dpsContent.style.display = 'none';
  3763. shieldContent.style.display = 'none';
  3764. armourContent.style.display = 'none';
  3765. evasionTab.style.opacity = '1';
  3766. defenceTab.style.opacity = '0.6';
  3767. dpsTab.style.opacity = '0.6';
  3768. shieldTab.style.opacity = '0.6';
  3769. armourTab.style.opacity = '0.6';
  3770. };
  3771. armourTab.onclick = () => {
  3772. armourTab.classList.add('active');
  3773. defenceTab.classList.remove('active');
  3774. dpsTab.classList.remove('active');
  3775. shieldTab.classList.remove('active');
  3776. evasionTab.classList.remove('active');
  3777. armourContent.style.display = 'block';
  3778. defenceContent.style.display = 'none';
  3779. dpsContent.style.display = 'none';
  3780. shieldContent.style.display = 'none';
  3781. evasionContent.style.display = 'none';
  3782. armourTab.style.opacity = '1';
  3783. defenceTab.style.opacity = '0.6';
  3784. dpsTab.style.opacity = '0.6';
  3785. shieldTab.style.opacity = '0.6';
  3786. evasionTab.style.opacity = '0.6';
  3787. };
  3788.  
  3789. // 添加滚动条样式
  3790. const style = document.createElement('style');
  3791. style.textContent = `
  3792. #dps-sort-content::-webkit-scrollbar,
  3793. #shield-sort-content::-webkit-scrollbar,
  3794. #evasion-sort-content::-webkit-scrollbar,
  3795. #armour-sort-content::-webkit-scrollbar {
  3796. width: 6px;
  3797. }
  3798. #dps-sort-content::-webkit-scrollbar-track,
  3799. #shield-sort-content::-webkit-scrollbar-track,
  3800. #evasion-sort-content::-webkit-scrollbar-track {
  3801. background: #1a1a1a;
  3802. }
  3803. #dps-sort-content::-webkit-scrollbar-thumb,
  3804. #shield-sort-content::-webkit-scrollbar-thumb,
  3805. #evasion-sort-content::-webkit-scrollbar-thumb {
  3806. background: #444;
  3807. border-radius: 3px;
  3808. }
  3809. #dps-sort-content::-webkit-scrollbar-thumb:hover,
  3810. #shield-sort-content::-webkit-scrollbar-thumb:hover,
  3811. #evasion-sort-content::-webkit-scrollbar-thumb:hover {
  3812. background: #555;
  3813. }
  3814. .dps-item {
  3815. padding: 4px 8px;
  3816. margin: 1px 0;
  3817. background: #2d2d2d;
  3818. border-radius: 3px;
  3819. cursor: pointer;
  3820. transition: background 0.2s;
  3821. display: flex;
  3822. justify-content: space-between;
  3823. align-items: center;
  3824. gap: 8px;
  3825. font-size: 13px;
  3826. white-space: nowrap;
  3827. user-select: none;
  3828. line-height: 1.2;
  3829. }
  3830. .dps-item:hover {
  3831. background: #3d3d3d;
  3832. }
  3833. .dps-item:last-child {
  3834. margin-bottom: 0;
  3835. }
  3836. .dps-value {
  3837. color: #FFD700;
  3838. font-weight: bold;
  3839. }
  3840. .shield-value {
  3841. color: #8acdff;
  3842. font-weight: bold;
  3843. }
  3844. .evasion-value {
  3845. color: #7FFF00;
  3846. font-weight: bold;
  3847. }
  3848. .price-value {
  3849. color: #8acdff;
  3850. font-size: 12px;
  3851. text-align: right;
  3852. }
  3853. `;
  3854.  
  3855. // 组装面板
  3856. panel.appendChild(titleBar);
  3857. panel.appendChild(contentContainer);
  3858. document.head.appendChild(style);
  3859. document.body.appendChild(panel);
  3860.  
  3861. console.log('Integrated panel created successfully');
  3862. return panel;
  3863. }
  3864.  
  3865. // 计算闪避值
  3866. function calculateEvasion() {
  3867. console.log('Calculating Evasion...');
  3868. const items = document.querySelectorAll('.row[data-id]');
  3869. console.log('Found items:', items.length);
  3870.  
  3871. let processedCount = 0;
  3872. let successCount = 0;
  3873.  
  3874. items.forEach((item, index) => {
  3875. try {
  3876. // 如果已经计算过闪避值,则跳过
  3877. if (item.querySelector('.evasion-display')) {
  3878. processedCount++;
  3879. return;
  3880. }
  3881.  
  3882. // 1. 获取符文孔数量
  3883. const socketsDiv = item.querySelector('.sockets');
  3884. if (!socketsDiv) {
  3885. console.debug(`Item ${index}: No sockets div found`);
  3886. return;
  3887. }
  3888.  
  3889. // 修改符文孔数量的获取逻辑
  3890. let numSockets = 0;
  3891. for (let i = 1; i <= 6; i++) { // 假设最多6个符文孔
  3892. if (socketsDiv.classList.contains(`numSockets${i}`)) {
  3893. numSockets = i;
  3894. break;
  3895. }
  3896. }
  3897. if (numSockets === 0) {
  3898. // 尝试其他方式获取符文孔数量
  3899. const socketText = socketsDiv.textContent.trim();
  3900. if (socketText) {
  3901. numSockets = socketText.length;
  3902. } else {
  3903. numSockets = 1; // 默认值
  3904. }
  3905. console.debug(`Item ${index}: Using alternative socket count: ${numSockets}`);
  3906. }
  3907.  
  3908. // 2. 获取闪避值
  3909. const evasionSpan = item.querySelector('span[data-field="ev"]');
  3910. if (!evasionSpan) {
  3911. console.debug(`Item ${index}: No Evasion span found`);
  3912. return;
  3913. }
  3914.  
  3915. const evasionValue = evasionSpan.querySelector('.colourAugmented, .colourDefault');
  3916. if (!evasionValue) {
  3917. console.debug(`Item ${index}: No Evasion value found`);
  3918. return;
  3919. }
  3920.  
  3921. const totalEvasion = parseInt(evasionValue.textContent);
  3922. if (isNaN(totalEvasion)) {
  3923. console.debug(`Item ${index}: Invalid Evasion value: ${evasionValue.textContent}`);
  3924. return;
  3925. }
  3926.  
  3927. // 3. 获取当前品质
  3928. let currentQuality = 0;
  3929. const qualitySpan = item.querySelector('span[data-field="quality"]');
  3930. if (qualitySpan) {
  3931. const qualityMatch = qualitySpan.textContent.match(/\+(\d+)%/);
  3932. if (qualityMatch) {
  3933. currentQuality = parseInt(qualityMatch[1]);
  3934. }
  3935. }
  3936. // 计算品质差值
  3937. const qualityDiff = 20 - currentQuality;
  3938.  
  3939. // 4. 获取闪避增加百分比
  3940. let evasionInc = 0;
  3941.  
  3942. // 定义可能包含闪避增加的词缀ID列表
  3943. const possibleEvasionIncIds = [
  3944. 'stat.explicit.stat_1999113824', // 增加闪避与能量护盾
  3945. 'stat.explicit.stat_2144192051', // 增加闪避
  3946. 'stat.explicit.stat_3489782002', // 增加闪避和生命
  3947. 'stat.explicit.stat_3321629045' // 增加护甲与闪避
  3948. ];
  3949.  
  3950. // 遍历所有可能的词缀ID
  3951. for (const statId of possibleEvasionIncIds) {
  3952. const statSpan = item.querySelector(`span[data-field="${statId}"]`);
  3953. if (statSpan) {
  3954. // 检查词缀文本是否包含"闪避"或"Evasion"
  3955. const statText = statSpan.textContent;
  3956. if (statText.includes('闪避') || statText.includes('Evasion')) {
  3957. // 提取百分比数值
  3958. const incMatch = statText.match(/(\d+)%/);
  3959. if (incMatch) {
  3960. evasionInc += parseInt(incMatch[1]);
  3961. console.debug(`Found Evasion increase: ${incMatch[1]}% from stat ${statId}`);
  3962. }
  3963. }
  3964. }
  3965. }
  3966.  
  3967. // 通用方法:查找所有包含"闪避"或"Evasion"的显式词缀
  3968. const allExplicitStats = item.querySelectorAll('span[data-field^="stat.explicit.stat_"]');
  3969. allExplicitStats.forEach(statSpan => {
  3970. // 检查是否已经在上面的特定ID列表中处理过
  3971. const statId = statSpan.getAttribute('data-field');
  3972. if (!possibleEvasionIncIds.includes(statId)) {
  3973. const statText = statSpan.textContent;
  3974. // 检查是否包含闪避相关文本和百分比增加
  3975. if ((statText.includes('闪避') || statText.includes('Evasion')) &&
  3976. statText.includes('%') &&
  3977. (statText.includes('增加') || statText.includes('increased'))) {
  3978. const incMatch = statText.match(/(\d+)%/);
  3979. if (incMatch) {
  3980. evasionInc += parseInt(incMatch[1]);
  3981. console.debug(`Found additional Evasion increase: ${incMatch[1]}% from stat ${statId}`);
  3982. // 将新发现的ID添加到列表中,以便将来使用
  3983. possibleEvasionIncIds.push(statId);
  3984. }
  3985. }
  3986. }
  3987. });
  3988.  
  3989. // 5. 获取已插入的符文提供的增益和数量
  3990. let insertedRuneBonus = 0;
  3991. let insertedRuneCount = 0;
  3992. let evasionRuneInc = 0; // 专门用于记录闪避符文的增益
  3993.  
  3994. // 获取符文增益 - 闪避增加
  3995. const evasionRuneStatSpan = item.querySelector('span[data-field="stat.rune.stat_3523867985"]');
  3996. if (evasionRuneStatSpan) {
  3997. const runeMatch = evasionRuneStatSpan.textContent.match(/(\d+)%/);
  3998. if (runeMatch) {
  3999. insertedRuneBonus = parseInt(runeMatch[1]);
  4000. insertedRuneCount = insertedRuneBonus / 20;
  4001. }
  4002. }
  4003.  
  4004. // 获取符文增益 - 闪避百分比增加
  4005. const evasionIncRuneStatSpan = item.querySelector('span[data-field="stat.rune.stat_2866361420"]');
  4006. if (evasionIncRuneStatSpan) {
  4007. const evasionIncRuneMatch = evasionIncRuneStatSpan.textContent.match(/(\d+)%/);
  4008. if (evasionIncRuneMatch) {
  4009. evasionRuneInc = parseInt(evasionIncRuneMatch[1]);
  4010. }
  4011. }
  4012.  
  4013. // 6. 计算可用的符文孔位增益
  4014. let availableRuneBonus = 0;
  4015. // 计算剩余可用的孔数
  4016. const remainingSlots = numSockets - insertedRuneCount;
  4017. availableRuneBonus = remainingSlots * 20;
  4018.  
  4019. // 7. 计算基础闪避
  4020. // 当前闪避 = 基础闪避 * (1 + 当前品质/100) * (1 + (已插入符文增益 + 装备增益 + 闪避符文增益)/100)
  4021. const baseEvasion = Math.round(totalEvasion / (1 + currentQuality/100) / (1 + (insertedRuneBonus + evasionInc + evasionRuneInc)/100));
  4022.  
  4023. // 8. 计算最大可能闪避(考虑品质提升和剩余符文孔)
  4024. // 最大闪避 = 基础闪避 * (1 + (当前品质 + 品质差值)/100) * (1 + (装备增益 + 已插入符文增益 + 可用符文增益)/100)
  4025. const maxEvasion = Math.round(baseEvasion * (1 + (currentQuality + qualityDiff)/100) * (1 + (evasionInc + insertedRuneBonus + availableRuneBonus)/100));
  4026.  
  4027. // 创建闪避显示元素
  4028. const evasionDisplay = document.createElement('span');
  4029. evasionDisplay.className = 'evasion-display';
  4030. evasionDisplay.style.cssText = `
  4031. color: #32CD32;
  4032. font-weight: bold;
  4033. margin-left: 10px;
  4034. `;
  4035. evasionDisplay.textContent = `总闪避: ${maxEvasion}`;
  4036.  
  4037. // 将闪避显示添加到闪避后面
  4038. evasionSpan.appendChild(evasionDisplay);
  4039.  
  4040. successCount++;
  4041. processedCount++;
  4042.  
  4043. // 添加调试信息
  4044. console.debug(`Item ${index} Evasion calculation:`, {
  4045. totalEvasion,
  4046. currentQuality,
  4047. qualityDiff,
  4048. evasionInc,
  4049. insertedRuneBonus,
  4050. insertedRuneCount,
  4051. numSockets,
  4052. availableRuneBonus,
  4053. baseEvasion,
  4054. maxEvasion
  4055. });
  4056. } catch (error) {
  4057. console.error(`Error calculating evasion for item ${index}:`, error);
  4058. }
  4059. });
  4060.  
  4061. console.log(`Evasion calculation completed: ${successCount} successful, ${processedCount} processed out of ${items.length} items`);
  4062.  
  4063. // 更新闪避排序面板
  4064. updateEvasionPanel();
  4065. }
  4066.  
  4067. // 创建闪避排序面板
  4068. function createEvasionPanel() {
  4069. const panel = document.createElement('div');
  4070. panel.id = 'evasion-sort-panel';
  4071. panel.style.cssText = `
  4072. position: fixed;
  4073. right: 20px;
  4074. top: 50%;
  4075. transform: translateY(calc(-50% + 400px));
  4076. background: rgba(28, 28, 28, 0.95);
  4077. padding: 8px;
  4078. border-radius: 6px;
  4079. box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
  4080. border: 1px solid #444;
  4081. width: 180px;
  4082. max-height: 60vh;
  4083. z-index: 9997;
  4084. display: none;
  4085. `;
  4086.  
  4087. // 添加标题
  4088. const title = document.createElement('div');
  4089. title.style.cssText = `
  4090. font-weight: bold;
  4091. color: #7FFF00;
  4092. margin-bottom: 6px;
  4093. padding-bottom: 3px;
  4094. border-bottom: 1px solid #444;
  4095. display: flex;
  4096. justify-content: space-between;
  4097. align-items: center;
  4098. cursor: pointer;
  4099. font-size: 14px;
  4100. user-select: none;
  4101. height: 18px;
  4102. `;
  4103.  
  4104. // 添加展开/收起指示器
  4105. const indicator = document.createElement('span');
  4106. indicator.textContent = '▼';
  4107. indicator.style.marginRight = '3px';
  4108. indicator.style.fontSize = '10px';
  4109. indicator.id = 'integrated-panel-indicator';
  4110.  
  4111. const titleText = document.createElement('span');
  4112. titleText.textContent = 'EV';
  4113.  
  4114. const titleLeft = document.createElement('div');
  4115. titleLeft.style.display = 'flex';
  4116. titleLeft.style.alignItems = 'center';
  4117. titleLeft.appendChild(indicator);
  4118. titleLeft.appendChild(titleText);
  4119.  
  4120. title.appendChild(titleLeft);
  4121.  
  4122. // 添加关闭按钮
  4123. const closeBtn = document.createElement('button');
  4124. closeBtn.textContent = '×';
  4125. closeBtn.style.cssText = `
  4126. background: none;
  4127. border: none;
  4128. color: #999;
  4129. font-size: 20px;
  4130. cursor: pointer;
  4131. padding: 0 5px;
  4132. `;
  4133. closeBtn.onclick = (e) => {
  4134. e.stopPropagation();
  4135. panel.style.display = 'none';
  4136. };
  4137. title.appendChild(closeBtn);
  4138.  
  4139. // 添加内容容器
  4140. const content = document.createElement('div');
  4141. content.id = 'evasion-sort-content';
  4142. content.style.cssText = `
  4143. max-height: calc(60vh - 35px);
  4144. overflow-y: auto;
  4145. transition: max-height 0.3s ease-out;
  4146. padding-right: 2px;
  4147. `;
  4148.  
  4149. // 添加展开/收起功能
  4150. let isExpanded = true;
  4151. title.onclick = () => {
  4152. isExpanded = !isExpanded;
  4153. content.style.maxHeight = isExpanded ? 'calc(60vh - 35px)' : '0';
  4154. content.style.overflow = isExpanded ? 'auto' : 'hidden';
  4155. indicator.textContent = isExpanded ? '▼' : '▶';
  4156. };
  4157.  
  4158. // 添加组件到面板
  4159. panel.appendChild(title);
  4160. panel.appendChild(content);
  4161.  
  4162. // 添加到文档
  4163. document.body.appendChild(panel);
  4164.  
  4165. console.log('Evasion panel created successfully');
  4166. return panel;
  4167. }
  4168.  
  4169. // 更新闪避排序面板
  4170. function updateEvasionPanel() {
  4171. try {
  4172. console.log('Updating evasion panel...');
  4173.  
  4174. // 获取或创建面板
  4175. const panel = document.getElementById('integrated-sort-panel') || createIntegratedPanel();
  4176. const content = document.getElementById('evasion-sort-content');
  4177. if (!content) {
  4178. console.error('Evasion sort content not found');
  4179. return;
  4180. }
  4181.  
  4182. // 清空现有内容
  4183. content.innerHTML = '';
  4184.  
  4185. // 清除可能存在的旧数据属性
  4186. content.removeAttribute('data-armour-content');
  4187. content.removeAttribute('data-shield-content');
  4188. content.removeAttribute('data-dps-content');
  4189. content.removeAttribute('data-defence-content');
  4190.  
  4191. // 获取所有物品
  4192. const items = document.querySelectorAll('.row[data-id]');
  4193. const evasionData = [];
  4194.  
  4195. // 收集闪避数据
  4196. items.forEach(item => {
  4197. const evasionDisplay = item.querySelector('.evasion-display');
  4198. if (evasionDisplay) {
  4199. const evasion = parseInt(evasionDisplay.textContent.replace('总闪避: ', ''));
  4200.  
  4201. // 获取价格
  4202. const priceElement = item.querySelector('.price [data-field="price"]');
  4203. let price = '未标价';
  4204. if (priceElement) {
  4205. const amount = priceElement.querySelector('span:not(.price-label):not(.currency-text)');
  4206. const currencyText = priceElement.querySelector('.currency-text');
  4207. if (amount && currencyText) {
  4208. const amountText = amount.textContent.trim();
  4209. const currency = currencyText.querySelector('span')?.textContent || currencyText.textContent;
  4210. // 使用全局的convertCurrencyText函数
  4211. const simpleCurrency = convertCurrencyText(currency);
  4212. price = `${amountText}${simpleCurrency}`;
  4213. }
  4214. }
  4215.  
  4216. evasionData.push({
  4217. evasion,
  4218. price,
  4219. element: item
  4220. });
  4221. }
  4222. });
  4223.  
  4224. console.log(`Found ${evasionData.length} items with evasion data`);
  4225.  
  4226. // 如果没有闪避数据,隐藏闪避选项卡
  4227. const evasionTab = panel.querySelector('[data-tab="evasion"]');
  4228. if (evasionData.length === 0) {
  4229. if (evasionTab) evasionTab.style.display = 'none';
  4230.  
  4231. // 如果其他选项卡也是隐藏的,则隐藏整个面板
  4232. const dpsTab = panel.querySelector('[data-tab="dps"]');
  4233. const shieldTab = panel.querySelector('[data-tab="shield"]');
  4234. const armourTab = panel.querySelector('[data-tab="armour"]');
  4235. const defenceTab = panel.querySelector('[data-tab="defence"]');
  4236. // 检查是否所有选项卡都隐藏了
  4237. if ((dpsTab && dpsTab.style.display === 'none') &&
  4238. (shieldTab && shieldTab.style.display === 'none') &&
  4239. (armourTab && armourTab.style.display === 'none') &&
  4240. (defenceTab && defenceTab.style.display === 'none')) {
  4241. panel.style.display = 'none';
  4242. }
  4243. // 移除自动切换到其他选项卡的代码,保留当前选项卡状态
  4244. return;
  4245. } else {
  4246. // 有闪避数据,确保闪避选项卡可见
  4247. if (evasionTab) evasionTab.style.display = '';
  4248. }
  4249.  
  4250. // 按闪避值从高到低排序
  4251. evasionData.sort((a, b) => b.evasion - a.evasion);
  4252.  
  4253. // 更新面板内容
  4254. evasionData.forEach(({evasion, price, element}) => {
  4255. const evasionItem = document.createElement('div');
  4256. evasionItem.className = 'dps-item'; // 复用DPS项的样式
  4257.  
  4258. // 创建闪避显示
  4259. const evasionText = document.createElement('span');
  4260. evasionText.className = 'evasion-value'; // 使用闪避专用的样式
  4261. evasionText.textContent = evasion.toString();
  4262.  
  4263. // 创建价格显示
  4264. const priceText = document.createElement('span');
  4265. priceText.className = 'price-value';
  4266. priceText.textContent = price;
  4267. // 添加到闪避项
  4268. evasionItem.appendChild(evasionText);
  4269. evasionItem.appendChild(priceText);
  4270. // 添加点击事件
  4271. evasionItem.onclick = () => {
  4272. element.scrollIntoView({ behavior: 'smooth', block: 'center' });
  4273. // 添加高亮效果
  4274. element.style.transition = 'background-color 0.3s';
  4275. element.style.backgroundColor = 'rgba(127, 255, 0, 0.2)';
  4276. setTimeout(() => {
  4277. element.style.backgroundColor = '';
  4278. }, 1500);
  4279. };
  4280.  
  4281. content.appendChild(evasionItem);
  4282. });
  4283.  
  4284. // 显示面板
  4285. panel.style.display = 'block';
  4286. console.log('Evasion panel updated successfully');
  4287. } catch (error) {
  4288. console.error('Error updating evasion panel:', error);
  4289. }
  4290. }
  4291.  
  4292. // 计算护甲值
  4293. function calculateArmour() {
  4294. console.log('Calculating Armour...');
  4295. const items = document.querySelectorAll('.row[data-id]');
  4296. console.log('Found items:', items.length);
  4297.  
  4298. let processedCount = 0;
  4299. let successCount = 0;
  4300.  
  4301. items.forEach((item, index) => {
  4302. try {
  4303. // 如果已经计算过护甲值,则跳过
  4304. if (item.querySelector('.armour-display')) {
  4305. processedCount++;
  4306. return;
  4307. }
  4308.  
  4309. // 1. 获取符文孔数量
  4310. const socketsDiv = item.querySelector('.sockets');
  4311. if (!socketsDiv) {
  4312. console.debug(`Item ${index}: No sockets div found`);
  4313. return;
  4314. }
  4315.  
  4316. // 修改符文孔数量的获取逻辑
  4317. let numSockets = 0;
  4318. for (let i = 1; i <= 6; i++) { // 假设最多6个符文孔
  4319. if (socketsDiv.classList.contains(`numSockets${i}`)) {
  4320. numSockets = i;
  4321. break;
  4322. }
  4323. }
  4324. if (numSockets === 0) {
  4325. // 尝试其他方式获取符文孔数量
  4326. const socketText = socketsDiv.textContent.trim();
  4327. if (socketText) {
  4328. numSockets = socketText.length;
  4329. } else {
  4330. numSockets = 1; // 默认值
  4331. }
  4332. console.debug(`Item ${index}: Using alternative socket count: ${numSockets}`);
  4333. }
  4334.  
  4335. // 2. 获取护甲值
  4336. const armourSpan = item.querySelector('span[data-field="ar"]');
  4337. if (!armourSpan) {
  4338. console.debug(`Item ${index}: No Armour span found`);
  4339. return;
  4340. }
  4341.  
  4342. const armourValue = armourSpan.querySelector('.colourAugmented, .colourDefault');
  4343. if (!armourValue) {
  4344. console.debug(`Item ${index}: No Armour value found`);
  4345. return;
  4346. }
  4347.  
  4348. const totalArmour = parseInt(armourValue.textContent);
  4349. if (isNaN(totalArmour)) {
  4350. console.debug(`Item ${index}: Invalid Armour value: ${armourValue.textContent}`);
  4351. return;
  4352. }
  4353.  
  4354. // 3. 获取当前品质
  4355. let currentQuality = 0;
  4356. const qualitySpan = item.querySelector('span[data-field="quality"]');
  4357. if (qualitySpan) {
  4358. const qualityMatch = qualitySpan.textContent.match(/\+(\d+)%/);
  4359. if (qualityMatch) {
  4360. currentQuality = parseInt(qualityMatch[1]);
  4361. }
  4362. }
  4363. // 计算品质差值
  4364. const qualityDiff = 20 - currentQuality;
  4365.  
  4366. // 4. 获取护甲增加百分比
  4367. let armourInc = 0;
  4368.  
  4369. // 定义可能包含护甲增加的词缀ID列表
  4370. const possibleArmourIncIds = [
  4371. 'stat.explicit.stat_3321629045', // 增加护甲与能量护盾
  4372. 'stat.explicit.stat_2866361420', // 增加护甲
  4373. 'stat.explicit.stat_2511217560', // 增加最大护甲
  4374. 'stat.explicit.stat_3489782002' // 增加护甲和生命
  4375. ];
  4376.  
  4377. // 遍历所有可能的词缀ID
  4378. for (const statId of possibleArmourIncIds) {
  4379. const statSpan = item.querySelector(`span[data-field="${statId}"]`);
  4380. if (statSpan) {
  4381. // 检查词缀文本是否包含"护甲"或"Armour"
  4382. const statText = statSpan.textContent;
  4383. if (statText.includes('护甲') || statText.includes('Armour')) {
  4384. // 提取百分比数值
  4385. const incMatch = statText.match(/(\d+)%/);
  4386. if (incMatch) {
  4387. armourInc += parseInt(incMatch[1]);
  4388. console.debug(`Found Armour increase: ${incMatch[1]}% from stat ${statId}`);
  4389. }
  4390. }
  4391. }
  4392. }
  4393.  
  4394. // 通用方法:查找所有包含"护甲"或"Armour"的显式词缀
  4395. const allExplicitStats = item.querySelectorAll('span[data-field^="stat.explicit.stat_"]');
  4396. allExplicitStats.forEach(statSpan => {
  4397. // 检查是否已经在上面的特定ID列表中处理过
  4398. const statId = statSpan.getAttribute('data-field');
  4399. if (!possibleArmourIncIds.includes(statId)) {
  4400. const statText = statSpan.textContent;
  4401. // 检查是否包含护甲相关文本和百分比增加
  4402. if ((statText.includes('护甲') || statText.includes('Armour')) &&
  4403. statText.includes('%') &&
  4404. (statText.includes('增加') || statText.includes('increased'))) {
  4405. const incMatch = statText.match(/(\d+)%/);
  4406. if (incMatch) {
  4407. armourInc += parseInt(incMatch[1]);
  4408. console.debug(`Found additional Armour increase: ${incMatch[1]}% from stat ${statId}`);
  4409. // 将新发现的ID添加到列表中,以便将来使用
  4410. possibleArmourIncIds.push(statId);
  4411. }
  4412. }
  4413. }
  4414. });
  4415.  
  4416. // 5. 获取已插入的符文提供的增益和数量
  4417. let insertedRuneBonus = 0;
  4418. let insertedRuneCount = 0;
  4419. let armourRuneInc = 0; // 专门用于记录护甲符文的增益
  4420.  
  4421. // 获取符文增益 - 护甲增加
  4422. const armourRuneStatSpan = item.querySelector('span[data-field="stat.rune.stat_3523867985"]');
  4423. if (armourRuneStatSpan) {
  4424. const runeMatch = armourRuneStatSpan.textContent.match(/(\d+)%/);
  4425. if (runeMatch) {
  4426. insertedRuneBonus = parseInt(runeMatch[1]);
  4427. insertedRuneCount = insertedRuneBonus / 20;
  4428. }
  4429. }
  4430.  
  4431. // 获取符文增益 - 护甲百分比增加
  4432. const armourIncRuneStatSpan = item.querySelector('span[data-field="stat.rune.stat_2866361420"]');
  4433. if (armourIncRuneStatSpan) {
  4434. const armourIncRuneMatch = armourIncRuneStatSpan.textContent.match(/(\d+)%/);
  4435. if (armourIncRuneMatch) {
  4436. armourRuneInc = parseInt(armourIncRuneMatch[1]);
  4437. }
  4438. }
  4439.  
  4440. // 6. 计算可用的符文孔位增益
  4441. let availableRuneBonus = 0;
  4442. // 计算剩余可用的孔数
  4443. const remainingSlots = numSockets - insertedRuneCount;
  4444. availableRuneBonus = remainingSlots * 20;
  4445.  
  4446. // 7. 计算基础护甲
  4447. // 当前护甲 = 基础护甲 * (1 + 当前品质/100) * (1 + (已插入符文增益 + 装备增益 + 护甲符文增益)/100)
  4448. const baseArmour = Math.round(totalArmour / (1 + currentQuality/100) / (1 + (insertedRuneBonus + armourInc + armourRuneInc)/100));
  4449.  
  4450. // 8. 计算最大可能护甲(考虑品质提升和剩余符文孔)
  4451. // 最大护甲 = 基础护甲 * (1 + (当前品质 + 品质差值)/100) * (1 + (装备增益 + 已插入符文增益 + 可用符文增益)/100)
  4452. const maxArmour = Math.round(baseArmour * (1 + (currentQuality + qualityDiff)/100) * (1 + (armourInc + insertedRuneBonus + availableRuneBonus)/100));
  4453.  
  4454. // 创建护甲显示元素
  4455. const armourDisplay = document.createElement('span');
  4456. armourDisplay.className = 'armour-display';
  4457. armourDisplay.style.cssText = `
  4458. color: #CD853F;
  4459. font-weight: bold;
  4460. margin-left: 10px;
  4461. `;
  4462. armourDisplay.textContent = `总护甲: ${maxArmour}`;
  4463.  
  4464. // 将护甲显示添加到护甲后面
  4465. armourSpan.appendChild(armourDisplay);
  4466.  
  4467. successCount++;
  4468. processedCount++;
  4469.  
  4470. // 添加调试信息
  4471. console.debug(`Item ${index} Armour calculation:`, {
  4472. totalArmour,
  4473. currentQuality,
  4474. qualityDiff,
  4475. armourInc,
  4476. insertedRuneBonus,
  4477. insertedRuneCount,
  4478. numSockets,
  4479. availableRuneBonus,
  4480. baseArmour,
  4481. maxArmour
  4482. });
  4483. } catch (error) {
  4484. console.error(`Error calculating armour for item ${index}:`, error);
  4485. }
  4486. });
  4487.  
  4488. console.log(`Armour calculation completed: ${successCount} successful, ${processedCount} processed out of ${items.length} items`);
  4489.  
  4490. // 更新护甲排序面板
  4491. updateArmourPanel();
  4492. }
  4493.  
  4494. // 创建护甲排序面板
  4495. function createArmourPanel() {
  4496. const panel = document.createElement('div');
  4497. panel.id = 'armour-sort-panel';
  4498. panel.style.cssText = `
  4499. position: fixed;
  4500. right: 20px;
  4501. top: 50%;
  4502. transform: translateY(calc(-50% + 600px));
  4503. background: rgba(28, 28, 28, 0.95);
  4504. padding: 8px;
  4505. border-radius: 6px;
  4506. box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
  4507. border: 1px solid #444;
  4508. width: 180px;
  4509. max-height: 60vh;
  4510. z-index: 9997;
  4511. display: none;
  4512. `;
  4513.  
  4514. // 添加标题
  4515. const title = document.createElement('div');
  4516. title.style.cssText = `
  4517. font-weight: bold;
  4518. color: #FF6347;
  4519. margin-bottom: 6px;
  4520. padding-bottom: 3px;
  4521. border-bottom: 1px solid #444;
  4522. display: flex;
  4523. justify-content: space-between;
  4524. align-items: center;
  4525. cursor: pointer;
  4526. font-size: 14px;
  4527. user-select: none;
  4528. height: 18px;
  4529. `;
  4530.  
  4531. // 添加展开/收起指示器
  4532. const indicator = document.createElement('span');
  4533. indicator.textContent = '▼';
  4534. indicator.style.marginRight = '3px';
  4535. indicator.style.fontSize = '10px';
  4536. indicator.id = 'integrated-panel-indicator';
  4537.  
  4538. const titleText = document.createElement('span');
  4539. titleText.textContent = 'AR';
  4540.  
  4541. const titleLeft = document.createElement('div');
  4542. titleLeft.style.display = 'flex';
  4543. titleLeft.style.alignItems = 'center';
  4544. titleLeft.appendChild(indicator);
  4545. titleLeft.appendChild(titleText);
  4546.  
  4547. title.appendChild(titleLeft);
  4548.  
  4549. // 添加关闭按钮
  4550. const closeBtn = document.createElement('button');
  4551. closeBtn.textContent = '×';
  4552. closeBtn.style.cssText = `
  4553. background: none;
  4554. border: none;
  4555. color: #999;
  4556. font-size: 20px;
  4557. cursor: pointer;
  4558. padding: 0 5px;
  4559. `;
  4560. closeBtn.onclick = (e) => {
  4561. e.stopPropagation();
  4562. panel.style.display = 'none';
  4563. };
  4564. title.appendChild(closeBtn);
  4565.  
  4566. // 添加内容容器
  4567. const content = document.createElement('div');
  4568. content.id = 'armour-sort-content';
  4569. content.style.cssText = `
  4570. max-height: calc(60vh - 35px);
  4571. overflow-y: auto;
  4572. transition: max-height 0.3s ease-out;
  4573. padding-right: 2px;
  4574. `;
  4575.  
  4576. // 添加展开/收起功能
  4577. let isExpanded = true;
  4578. title.onclick = () => {
  4579. isExpanded = !isExpanded;
  4580. content.style.maxHeight = isExpanded ? 'calc(60vh - 35px)' : '0';
  4581. content.style.overflow = isExpanded ? 'auto' : 'hidden';
  4582. indicator.textContent = isExpanded ? '▼' : '▶';
  4583. };
  4584.  
  4585. // 添加组件到面板
  4586. panel.appendChild(title);
  4587. panel.appendChild(content);
  4588.  
  4589. // 添加到文档
  4590. document.body.appendChild(panel);
  4591.  
  4592. console.log('Armour panel created successfully');
  4593. return panel;
  4594. }
  4595.  
  4596. // 更新护甲排序面板
  4597. function updateArmourPanel() {
  4598. try {
  4599. console.log('Updating armour panel...');
  4600.  
  4601. // 获取或创建面板
  4602. const panel = document.getElementById('integrated-sort-panel') || createIntegratedPanel();
  4603. const content = document.getElementById('armour-sort-content');
  4604. if (!content) {
  4605. console.error('Armour sort content not found');
  4606. return;
  4607. }
  4608.  
  4609. // 清空现有内容
  4610. content.innerHTML = '';
  4611.  
  4612. // 获取所有物品
  4613. const items = document.querySelectorAll('.row[data-id]');
  4614. const armourData = [];
  4615.  
  4616. // 收集护甲数据
  4617. items.forEach(item => {
  4618. const armourDisplay = item.querySelector('.armour-display');
  4619. if (armourDisplay) {
  4620. const armour = parseInt(armourDisplay.textContent.replace('总护甲: ', ''));
  4621.  
  4622. // 获取价格
  4623. const priceElement = item.querySelector('.price [data-field="price"]');
  4624. let price = '未标价';
  4625. if (priceElement) {
  4626. const amount = priceElement.querySelector('span:not(.price-label):not(.currency-text)');
  4627. const currencyText = priceElement.querySelector('.currency-text');
  4628. if (amount && currencyText) {
  4629. const amountText = amount.textContent.trim();
  4630. const currency = currencyText.querySelector('span')?.textContent || currencyText.textContent;
  4631. // 使用全局的convertCurrencyText函数
  4632. const simpleCurrency = convertCurrencyText(currency);
  4633. price = `${amountText}${simpleCurrency}`;
  4634. }
  4635. }
  4636.  
  4637. armourData.push({
  4638. armour,
  4639. price,
  4640. element: item
  4641. });
  4642. }
  4643. });
  4644.  
  4645. console.log(`Found ${armourData.length} items with armour data`);
  4646.  
  4647. // 如果没有护甲数据,隐藏护甲选项卡
  4648. const armourTab = panel.querySelector('[data-tab="armour"]');
  4649. if (armourData.length === 0) {
  4650. if (armourTab) armourTab.style.display = 'none';
  4651.  
  4652. // 如果其他选项卡也是隐藏的,则隐藏整个面板
  4653. const dpsTab = panel.querySelector('[data-tab="dps"]');
  4654. const shieldTab = panel.querySelector('[data-tab="shield"]');
  4655. const evasionTab = panel.querySelector('[data-tab="evasion"]');
  4656. const defenceTab = panel.querySelector('[data-tab="defence"]');
  4657. // 检查是否所有选项卡都隐藏了
  4658. if ((dpsTab && dpsTab.style.display === 'none') &&
  4659. (shieldTab && shieldTab.style.display === 'none') &&
  4660. (evasionTab && evasionTab.style.display === 'none') &&
  4661. (defenceTab && defenceTab.style.display === 'none')) {
  4662. panel.style.display = 'none';
  4663. }
  4664. // 移除自动切换到其他选项卡的代码,保留当前选项卡状态
  4665. return;
  4666. } else {
  4667. // 有护甲数据,确保护甲选项卡可见
  4668. if (armourTab) armourTab.style.display = '';
  4669. }
  4670.  
  4671. // 按护甲值从高到低排序
  4672. armourData.sort((a, b) => b.armour - a.armour);
  4673.  
  4674. // 更新面板内容
  4675. armourData.forEach(({armour, price, element}) => {
  4676. const armourItem = document.createElement('div');
  4677. armourItem.className = 'dps-item'; // 复用DPS项的样式
  4678.  
  4679. // 创建护甲显示
  4680. const armourText = document.createElement('span');
  4681. armourText.className = 'armour-value'; // 使用护甲专用的样式
  4682. armourText.textContent = armour.toString();
  4683.  
  4684. // 创建价格显示
  4685. const priceText = document.createElement('span');
  4686. priceText.className = 'price-value';
  4687. priceText.textContent = price;
  4688.  
  4689. // 添加到护甲项
  4690. armourItem.appendChild(armourText);
  4691. armourItem.appendChild(priceText);
  4692.  
  4693. // 添加点击事件
  4694. armourItem.onclick = () => {
  4695. element.scrollIntoView({ behavior: 'smooth', block: 'center' });
  4696. // 添加高亮效果
  4697. element.style.transition = 'background-color 0.3s';
  4698. element.style.backgroundColor = 'rgba(255, 99, 71, 0.2)';
  4699. setTimeout(() => {
  4700. element.style.backgroundColor = '';
  4701. }, 1500);
  4702. };
  4703.  
  4704. content.appendChild(armourItem);
  4705. });
  4706.  
  4707. // 显示面板
  4708. panel.style.display = 'block';
  4709. console.log('Armour panel updated successfully');
  4710. } catch (error) {
  4711. console.error('Error updating armour panel:', error);
  4712. }
  4713. }
  4714.  
  4715. // 计算总防御值(护盾+闪避+护甲)
  4716. function calculateDefence() {
  4717. console.log('Calculating Defence...');
  4718. const items = document.querySelectorAll('.row[data-id]');
  4719. console.log('Found items:', items.length);
  4720. let processedCount = 0;
  4721. let successCount = 0;
  4722. items.forEach((item, index) => {
  4723. try {
  4724. // 如果已经计算过总防御值,则跳过
  4725. if (item.querySelector('.defence-display')) {
  4726. processedCount++;
  4727. return;
  4728. }
  4729.  
  4730. // 获取护盾、闪避和护甲值
  4731. let totalDefence = 0;
  4732. // 获取护盾值
  4733. const shieldDisplay = item.querySelector('.shield-display');
  4734. if (shieldDisplay) {
  4735. const shield = parseInt(shieldDisplay.textContent.replace('总护盾: ', ''));
  4736. if (!isNaN(shield)) {
  4737. totalDefence += shield;
  4738. }
  4739. }
  4740. // 获取闪避值
  4741. const evasionDisplay = item.querySelector('.evasion-display');
  4742. if (evasionDisplay) {
  4743. const evasion = parseInt(evasionDisplay.textContent.replace('总闪避: ', ''));
  4744. if (!isNaN(evasion)) {
  4745. totalDefence += evasion;
  4746. }
  4747. }
  4748. // 获取护甲值
  4749. const armourDisplay = item.querySelector('.armour-display');
  4750. if (armourDisplay) {
  4751. const armour = parseInt(armourDisplay.textContent.replace('总护甲: ', ''));
  4752. if (!isNaN(armour)) {
  4753. totalDefence += armour;
  4754. }
  4755. }
  4756.  
  4757. // 创建总防御显示元素
  4758. const defenceDisplay = document.createElement('span');
  4759. defenceDisplay.className = 'defence-display';
  4760. defenceDisplay.style.cssText = `
  4761. color: #9370DB;
  4762. font-weight: bold;
  4763. margin-left: 5px;
  4764. font-size: 14px;
  4765. background-color: rgba(147, 112, 219, 0.1);
  4766. padding: 1px 4px;
  4767. border-radius: 3px;
  4768. `;
  4769. defenceDisplay.textContent = `DEF: ${totalDefence}`;
  4770.  
  4771. // 将总防御显示添加到装备名称旁边
  4772. const itemHeader = item.querySelector('.itemHeader');
  4773. if (itemHeader) {
  4774. // 找到装备名称元素
  4775. const itemName = itemHeader.querySelector('.itemName');
  4776. if (itemName) {
  4777. // 添加到装备名称后面
  4778. itemName.appendChild(defenceDisplay);
  4779. } else {
  4780. // 如果找不到装备名称,直接添加到itemHeader
  4781. itemHeader.appendChild(defenceDisplay);
  4782. }
  4783. } else {
  4784. // 如果找不到itemHeader,尝试添加到其他位置
  4785. const esSpan = item.querySelector('span[data-field="es"]');
  4786. const evSpan = item.querySelector('span[data-field="ev"]');
  4787. const arSpan = item.querySelector('span[data-field="ar"]');
  4788. if (esSpan) {
  4789. esSpan.appendChild(defenceDisplay);
  4790. } else if (evSpan) {
  4791. evSpan.appendChild(defenceDisplay);
  4792. } else if (arSpan) {
  4793. arSpan.appendChild(defenceDisplay);
  4794. }
  4795. }
  4796. successCount++;
  4797. processedCount++;
  4798.  
  4799. // 添加调试信息
  4800. console.debug(`Item ${index} Defence calculation: ${totalDefence}`);
  4801. } catch (error) {
  4802. console.error(`Error calculating defence for item ${index}:`, error);
  4803. }
  4804. });
  4805.  
  4806. console.log(`Defence calculation completed: ${successCount} successful, ${processedCount} processed out of ${items.length} items`);
  4807.  
  4808. // 更新总防御排序面板
  4809. updateDefencePanel();
  4810. }
  4811.  
  4812. // 更新总防御排序面板
  4813. function updateDefencePanel() {
  4814. try {
  4815. console.log('Updating defence panel...');
  4816. // 获取或创建面板
  4817. const panel = document.getElementById('integrated-sort-panel') || createIntegratedPanel();
  4818. const content = document.getElementById('defence-sort-content');
  4819. if (!content) {
  4820. console.error('Defence sort content not found');
  4821. return;
  4822. }
  4823.  
  4824. // 清空现有内容
  4825. content.innerHTML = '';
  4826. // 清除可能存在的旧数据属性
  4827. content.removeAttribute('data-shield-content');
  4828. content.removeAttribute('data-evasion-content');
  4829. content.removeAttribute('data-armour-content');
  4830. content.removeAttribute('data-dps-content');
  4831.  
  4832. // 获取所有物品
  4833. const items = document.querySelectorAll('.row[data-id]');
  4834. const defenceData = [];
  4835.  
  4836. // 收集总防御数据
  4837. items.forEach(item => {
  4838. const defenceDisplay = item.querySelector('.defence-display');
  4839. if (defenceDisplay) {
  4840. const defence = parseInt(defenceDisplay.textContent.replace('DEF: ', ''));
  4841. // 获取价格
  4842. const priceElement = item.querySelector('.price [data-field="price"]');
  4843. let price = '未标价';
  4844. if (priceElement) {
  4845. const amount = priceElement.querySelector('span:not(.price-label):not(.currency-text)');
  4846. const currencyText = priceElement.querySelector('.currency-text');
  4847. if (amount && currencyText) {
  4848. const amountText = amount.textContent.trim();
  4849. const currency = currencyText.querySelector('span')?.textContent || currencyText.textContent;
  4850. // 使用全局的convertCurrencyText函数
  4851. const simpleCurrency = convertCurrencyText(currency);
  4852. price = `${amountText}${simpleCurrency}`;
  4853. }
  4854. }
  4855.  
  4856. defenceData.push({
  4857. defence,
  4858. price,
  4859. element: item
  4860. });
  4861. }
  4862. });
  4863.  
  4864. console.log(`Found ${defenceData.length} items with defence data`);
  4865.  
  4866. // 如果没有总防御数据,隐藏总防御选项卡
  4867. const defenceTab = panel.querySelector('[data-tab="defence"]');
  4868. if (defenceData.length === 0) {
  4869. if (defenceTab) defenceTab.style.display = 'none';
  4870. // 如果其他选项卡也是隐藏的,则隐藏整个面板
  4871. const dpsTab = panel.querySelector('[data-tab="dps"]');
  4872. const shieldTab = panel.querySelector('[data-tab="shield"]');
  4873. const evasionTab = panel.querySelector('[data-tab="evasion"]');
  4874. const armourTab = panel.querySelector('[data-tab="armour"]');
  4875. // 检查是否所有选项卡都隐藏了
  4876. if ((dpsTab && dpsTab.style.display === 'none') &&
  4877. (shieldTab && shieldTab.style.display === 'none') &&
  4878. (evasionTab && evasionTab.style.display === 'none') &&
  4879. (armourTab && armourTab.style.display === 'none')) {
  4880. panel.style.display = 'none';
  4881. }
  4882. // 移除自动切换到其他选项卡的代码,保留当前选项卡状态
  4883. return;
  4884. } else {
  4885. // 有总防御数据,确保总防御选项卡可见
  4886. if (defenceTab) defenceTab.style.display = '';
  4887. }
  4888.  
  4889. // 按总防御值从高到低排序
  4890. defenceData.sort((a, b) => b.defence - a.defence);
  4891.  
  4892. // 更新面板内容
  4893. defenceData.forEach(({defence, price, element}) => {
  4894. const defenceItem = document.createElement('div');
  4895. defenceItem.className = 'dps-item'; // 复用DPS项的样式
  4896. // 创建总防御显示
  4897. const defenceText = document.createElement('span');
  4898. defenceText.className = 'defence-value';
  4899. defenceText.textContent = defence.toString();
  4900. // 创建价格显示
  4901. const priceText = document.createElement('span');
  4902. priceText.className = 'price-value';
  4903. priceText.textContent = price;
  4904. // 添加到总防御项
  4905. defenceItem.appendChild(defenceText);
  4906. defenceItem.appendChild(priceText);
  4907. // 添加点击事件
  4908. defenceItem.onclick = () => {
  4909. element.scrollIntoView({ behavior: 'smooth', block: 'center' });
  4910. // 添加高亮效果
  4911. element.style.transition = 'background-color 0.3s';
  4912. element.style.backgroundColor = 'rgba(147, 112, 219, 0.2)';
  4913. setTimeout(() => {
  4914. element.style.backgroundColor = '';
  4915. }, 1500);
  4916. };
  4917.  
  4918. content.appendChild(defenceItem);
  4919. });
  4920.  
  4921. // 显示面板
  4922. panel.style.display = 'block';
  4923. console.log('Defence panel updated successfully');
  4924. } catch (error) {
  4925. console.error('Error updating defence panel:', error);
  4926. }
  4927. }
  4928. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址