Keylol Table Sort

对keylol琪露诺折扣贴表格按价格或折扣排序

  1. // ==UserScript==
  2. // @name Keylol Table Sort
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.33
  5. // @description 对keylol琪露诺折扣贴表格按价格或折扣排序
  6. // @author 冰雪聪明琪露诺
  7. // @match https://keylol.com/*
  8. // @grant none
  9. // ==/UserScript==
  10. (function() {
  11. 'use strict';
  12. let optionsContainer;
  13. // 用于存储原始的表格行顺序
  14. let originalRows = [];
  15.  
  16. const setupSort = () => {
  17. optionsContainer && optionsContainer.remove();
  18. const table = document.querySelector('.t_fsz table.t_table');
  19. if (!table) return;
  20. const priceHeader = [...table.rows[0].cells].find(cell => cell.textContent.includes('商店价格'));
  21. const reviewHeader = [...table.rows[0].cells].find(cell => cell.textContent.includes('游戏评价'));
  22. const nameHeader = [...table.rows[0].cells].find(cell => cell.textContent.includes('游戏名称'));
  23. if (!priceHeader || !reviewHeader || !nameHeader) return;
  24.  
  25. // 存储原始行顺序
  26. originalRows = [...table.rows].slice(1);
  27.  
  28. optionsContainer = document.createElement('div');
  29. Object.assign(optionsContainer.style, {
  30. position: 'absolute', display: 'none', background: 'white',
  31. border: '1px solid #ccc', padding: '5px', zIndex: 100
  32. });
  33. optionsContainer.className = 'keylol-sort-options';
  34.  
  35. const priceIndex = [...table.rows[0].cells].findIndex(cell => cell.textContent.includes('商店价格'));
  36. const reviewIndex = [...table.rows[0].cells].findIndex(cell => cell.textContent.includes('游戏评价'));
  37. const nameIndex = [...table.rows[0].cells].findIndex(cell => cell.textContent.includes('游戏名称'));
  38.  
  39. const createSortOptions = (header, options, index) => {
  40. let visible = false;
  41. header.addEventListener('click', e => {
  42. e.stopPropagation();
  43. if (visible) {
  44. optionsContainer.style.display = 'none';
  45. visible = false;
  46. } else {
  47. optionsContainer.innerHTML = '';
  48. options.forEach(option => {
  49. const opt = document.createElement('div');
  50. opt.textContent = option.text;
  51. opt.style.cssText = 'cursor: pointer; padding: 5px';
  52. opt.addEventListener('click', () => {
  53. sortTable(table, option.fn, index);
  54. optionsContainer.style.display = 'none';
  55. visible = false;
  56. });
  57. optionsContainer.appendChild(opt);
  58. });
  59. const { left, bottom } = header.getBoundingClientRect();
  60. const { pageXOffset, pageYOffset } = window;
  61. Object.assign(optionsContainer.style, { left: left + pageXOffset + 'px', top: bottom + pageYOffset + 'px', display: 'block' });
  62. visible = true;
  63. }
  64. });
  65. };
  66.  
  67. const priceSortOptions = [
  68. { text: '按价格排序(升序)', fn: (a, b, idx) => parseFloat(a.cells[idx].textContent.match(/¥(\d+\.\d+)/)[1]) - parseFloat(b.cells[idx].textContent.match(/¥(\d+\.\d+)/)[1]) },
  69. { text: '按价格排序(降序)', fn: (a, b, idx) => parseFloat(b.cells[idx].textContent.match(/¥(\d+\.\d+)/)[1]) - parseFloat(a.cells[idx].textContent.match(/¥(\d+\.\d+)/)[1]) },
  70. { text: '按折扣排序(升序)', fn: (a, b, idx) => parseFloat(a.cells[idx].textContent.match(/-(\d+)%/)[1]) - parseFloat(b.cells[idx].textContent.match(/-(\d+)%/)[1]) },
  71. { text: '按折扣排序(降序)', fn: (a, b, idx) => parseFloat(b.cells[idx].textContent.match(/-(\d+)%/)[1]) - parseFloat(a.cells[idx].textContent.match(/-(\d+)%/)[1]) }
  72. ];
  73.  
  74. const reviewSortOptions = [
  75. { text: '按好评率排序(升序)', fn: (a, b, idx) => {
  76. const ratingA = parseFloat(a.cells[idx].textContent.match(/(\d+)%/)[1]);
  77. const ratingB = parseFloat(b.cells[idx].textContent.match(/(\d+)%/)[1]);
  78. return ratingA === ratingB ?
  79. parseInt(a.cells[idx].textContent.match(/(\d+)篇/)[1]) - parseInt(b.cells[idx].textContent.match(/(\d+)篇/)[1]) :
  80. ratingA - ratingB;
  81. } },
  82. { text: '按好评率排序(降序)', fn: (a, b, idx) => {
  83. const ratingA = parseFloat(a.cells[idx].textContent.match(/(\d+)%/)[1]);
  84. const ratingB = parseFloat(b.cells[idx].textContent.match(/(\d+)%/)[1]);
  85. return ratingA === ratingB ?
  86. parseInt(b.cells[idx].textContent.match(/(\d+)篇/)[1]) - parseInt(a.cells[idx].textContent.match(/(\d+)篇/)[1]) :
  87. ratingB - ratingA;
  88. } },
  89. { text: '按评测数排序(升序)', fn: (a, b, idx) => parseInt(a.cells[idx].textContent.match(/(\d+)篇/)[1]) - parseInt(b.cells[idx].textContent.match(/(\d+)篇/)[1]) },
  90. { text: '按评测数排序(降序)', fn: (a, b, idx) => parseInt(b.cells[idx].textContent.match(/(\d+)篇/)[1]) - parseInt(a.cells[idx].textContent.match(/(\d+)篇/)[1]) }
  91. ];
  92.  
  93. const nameSortOptions = [
  94. {
  95. text: '恢复默认排序',
  96. fn: (table) => {
  97. while (table.rows.length > 1) table.deleteRow(1);
  98. originalRows.forEach(row => table.appendChild(row));
  99. }
  100. },
  101. { text: '按名称排序(升序)', fn: (a, b, idx) => a.cells[idx].textContent.localeCompare(b.cells[idx].textContent) },
  102. { text: '按名称排序(降序)', fn: (a, b, idx) => b.cells[idx].textContent.localeCompare(a.cells[idx].textContent) }
  103. ];
  104.  
  105. createSortOptions(priceHeader, priceSortOptions, priceIndex);
  106. createSortOptions(reviewHeader, reviewSortOptions, reviewIndex);
  107. createSortOptions(nameHeader, nameSortOptions, nameIndex);
  108. document.body.appendChild(optionsContainer);
  109.  
  110. document.addEventListener('click', e => {
  111. optionsContainer && !optionsContainer.contains(e.target) && (optionsContainer.style.display = 'none');
  112. });
  113. };
  114.  
  115. const sortTable = (table, sortFunction, index) => {
  116. if (sortFunction.length === 1) {
  117. // 如果是恢复默认排序
  118. sortFunction(table);
  119. } else {
  120. const rows = [...table.rows].slice(1);
  121. rows.sort((a, b) => sortFunction(a, b, index));
  122. while (table.rows.length > 1) table.deleteRow(1);
  123. rows.forEach(row => table.appendChild(row));
  124. }
  125. };
  126.  
  127. document.body.addEventListener('click', e => {
  128. if (e.target.closest('.tindex li, div[style*="text-align: center;margin-top: 10px;"] a')) {
  129. setTimeout(setupSort, 1000);
  130. }
  131. });
  132.  
  133. setTimeout(setupSort, 1000);
  134. })();

QingJ © 2025

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