洛谷提交记录显示优化

修改提交记录背景

  1. // ==UserScript==
  2. // @name 洛谷提交记录显示优化
  3. // @namespace https://github.com/chenyuxuan2009/luogu_submission_better
  4. // @version 2.19
  5. // @description 修改提交记录背景
  6. // @author 沉石鱼惊旋
  7. // @match *://www.luogu.com.cn/record/*
  8. // @match *://www.luogu.com.cn
  9. // @match *://www.luogu.com.cn/*
  10. // @run-at document-end
  11. // @grant GM_registerMenuCommand
  12. // @grant GM_setValue
  13. // @grant GM_getValue
  14. // @license MIT
  15. // ==/UserScript==
  16.  
  17. let opacity = localStorage.getItem("opacity") || 0.3;
  18. let replaceSidebarStatus = localStorage.getItem("replaceSidebarStatus") || "1";
  19. const jsdelivrOptions = [
  20. 'https://cdn.jsdelivr.net',
  21. 'https://jsdelivrcn.netlify.app',
  22. 'https://cdn.mengze.vip',
  23. 'https://cdn.bili33.top',
  24. 'https://www.jsdmirror.com',
  25. ];
  26. const themeOptions = [
  27. 'nailoong',
  28. 'andy',
  29. 'qqemoji',
  30. 'qqsuperemoji',
  31. 'mixed1',
  32. 'theresa'
  33. ];
  34. const themeLabels = {
  35. "nailoong": "奶龙",
  36. "andy": "安梦梦",
  37. "qqemoji": "QQ 大表情",
  38. "qqsuperemoji": "QQ 超级表情",
  39. "mixed1": "混搭 1",
  40. "theresa": "特蕾西娅"
  41. };
  42. const themeTypes = {
  43. "nailoong": "gif",
  44. "andy": "gif",
  45. "qqemoji": "gif",
  46. "qqsuperemoji": "gif",
  47. "mixed1": "gif",
  48. "theresa": "png"
  49. };
  50. let jsdelivr = localStorage.getItem("jsdelivr") || 'https://cdn.jsdelivr.net';
  51. let theme = localStorage.getItem("theme") || 'nailoong';
  52. function getImage(theme, x) {
  53. return themeOptions.includes(theme) ?
  54. `${jsdelivr}/gh/chenyuxuan2009/luogu_submission_better/theme/${theme}/${x}.${themeTypes[theme]}` :
  55. localStorage.getItem(`${x}`);
  56. }
  57. let statusKeys = [
  58. "AC", "WA", "TLE", "MLE", "RE",
  59. "OLE", "UKE", "Judging", "CE", "Waiting", "Unshown"
  60. ];
  61.  
  62. let statusLabels = {
  63. "AC": "AC 图片 URL",
  64. "WA": "WA 图片 URL",
  65. "TLE": "TLE 图片 URL",
  66. "MLE": "MLE 图片 URL",
  67. "RE": "RE 图片 URL",
  68. "OLE": "OLE 图片 URL",
  69. "UKE": "UKE 图片 URL",
  70. "Judging": "Judging 图片 URL",
  71. "CE": "CE 图片 URL",
  72. "Waiting": "Waiting 图片 URL",
  73. "Unshown": "Unshown 图片 URL"
  74. };
  75. let AC = getImage(theme, 'AC');
  76. let WA = getImage(theme, 'WA');
  77. let TLE = getImage(theme, 'TLE');
  78. let MLE = getImage(theme, 'MLE');
  79. let RE = getImage(theme, 'RE');
  80. let OLE = getImage(theme, 'OLE');
  81. let UKE = getImage(theme, 'UKE');
  82. let Judging = getImage(theme, 'Judging');
  83. let CE = getImage(theme, 'CE');
  84. let Waiting = getImage(theme, 'Waiting');
  85. let Unshown = getImage(theme, 'Unshown');
  86. let ACcol = `rgba(82, 196, 26, ${opacity})`;
  87. let WAcol = `rgba(231, 76, 60, ${opacity})`;
  88. let TLEcol = `rgba(5, 34, 66, ${opacity})`;
  89. let MLEcol = `rgba(5, 34, 66, ${opacity})`;
  90. let REcol = `rgba(157, 61, 207, ${opacity})`;
  91. let OLEcol = `rgba(5, 34, 66, ${opacity})`;
  92. let UKEcol = `rgba(14, 29, 105, ${opacity})`;
  93. let Judgingcol = `rgba(20, 85, 143, ${opacity})`;
  94. let CEcol = `rgba(250, 219, 20, ${opacity})`;
  95. let Waitingcol = `rgba(20, 85, 143, ${opacity})`;
  96. let Unshowncol = `rgba(38, 38, 38, ${opacity})`;
  97. let sta = [AC, WA, TLE, MLE, RE, OLE, UKE, Judging, CE, Waiting, Unshown];
  98. let col = [ACcol, WAcol, TLEcol, MLEcol, REcol, OLEcol, UKEcol, Judgingcol, CEcol, Waitingcol, Unshowncol];
  99. let txt = ["AC", "WA", "TLE", "MLE", "RE", "OLE", "UKE", "Judging", "CE", "WJ", "US"];
  100. function getCol(x) {
  101. return `background: linear-gradient(${col[x]}, ${col[x]}), url('${sta[x]}'); background-size: cover;`;
  102. }
  103. function subBetter() {
  104. let tc = document.getElementsByClassName('test-case');
  105. let len = tc.length;
  106. let firstSTA = -1;
  107. let ac = 0;
  108. let judging = 0;
  109. for (let i = 0; i < len; i += 1) {
  110. if (tc[i].id === 'luogu_submission_better_right_row') continue;
  111. if (tc[i].innerHTML.includes('spinner')) {
  112. judging = 1;
  113. tc[i].style = getCol(7);
  114. continue;
  115. }
  116. if (!tc[i].getElementsByClassName('status')[0]) continue;
  117. let status = tc[i].getElementsByClassName('status')[0].innerHTML;
  118. if (status.length > 2) status = status.substring(0, 2);
  119. let tmpSTA = -1;
  120. if (status === "AC") {
  121. tmpSTA = 0;
  122. } else if (status === "WA") {
  123. tmpSTA = 1;
  124. } else if (status === "TL") {
  125. tmpSTA = 2;
  126. } else if (status === "ML") {
  127. tmpSTA = 3;
  128. } else if (status === "RE") {
  129. tmpSTA = 4;
  130. } else if (status === "OL") {
  131. tmpSTA = 5;
  132. } else if (status === "UK") {
  133. tmpSTA = 6;
  134. }
  135. tc[i].style = getCol(tmpSTA);
  136. if (tmpSTA === 0) {
  137. ac = 1;
  138. }
  139. if (tmpSTA != 0 && firstSTA === -1) {
  140. firstSTA = tmpSTA;
  141. }
  142. }
  143. if (judging) firstSTA = 7;
  144. if (firstSTA === -1 && ac) firstSTA = 0;
  145. if (replaceSidebarStatus === "1") {
  146. let doc = document.querySelector('div.info-rows');
  147. let id = -1;
  148. if (!doc) return;
  149. for (let i = 0; i < doc.children.length; i += 1) {
  150. if (doc.children[i].children[0].children[0].innerHTML.includes('评测状态')) {
  151. id = i;
  152. break;
  153. }
  154. }
  155. let info = document.getElementsByClassName('info-rows')[0].children[id].children[1];;
  156. if (info.innerText.includes('Judging')) firstSTA = 7;
  157. if (info.innerText.includes('Compile Error')) firstSTA = 8;
  158. if (info.innerText.includes('Unknown Error')) firstSTA = 6;
  159. if (info.innerText.includes('Waiting')) firstSTA = 9;
  160. if (info.innerText.includes('Unshown')) firstSTA = 10;
  161. if (firstSTA === -1) return;
  162. // info.innerHTML = `${firstSTA}`;
  163. // info.innerHTML = `${txt[firstSTA]}`;
  164. // return;
  165. if (firstSTA == 7) {
  166. if (!info.innerHTML.includes('spinner')) {
  167. info.innerHTML = `<div data-v-21e0a7cc="" class="test-case" style="${getCol(firstSTA)}" id="luogu_submission_better_right_row"><div data-v-21e0a7cc="" class="content"><div data-v-bbdab89a="" data-v-21e0a7cc="" class="spinner" style="width: 32px; height: 32px;"><div data-v-bbdab89a="" style="width: 32px; height: 32px; border-width: 2px;"></div></div></div></div>`
  168. }
  169. }
  170. else {
  171. if (!info.innerText.includes(txt[firstSTA])) {
  172. info.innerHTML = `<div data-v-21e0a7cc="" class="test-case" style="${getCol(firstSTA)}" id="luogu_submission_better_right_row"><div data-v-21e0a7cc="" class="content"><div data-v-21e0a7cc="" class="status">${txt[firstSTA]}</div></div> </div>`;
  173. }
  174. }
  175. }
  176. }
  177. function addButton() {
  178. function createSettingsPopup() {
  179. // 如果已经存在弹窗,避免重复创建
  180. if (document.getElementById('settingsPopup')) return;
  181.  
  182. // 创建悬浮窗口
  183. let popup = document.createElement("div");
  184. popup.id = "settingsPopup";
  185. popup.innerHTML = `
  186. <div class="popup-header">
  187. <span>插件设置</span>
  188. <button id="closePopup">✖</button>
  189. </div>
  190. <p>调整纯色背景的透明度:</p>
  191. <input type="number" id="opacityInput" min="0" max="1" step="0.1" value="${localStorage.getItem("opacity") || 0.3}">
  192. <button id="saveOpacity">保存透明度设置</button>
  193. <p>选择 jsdelivr 源服务器:<br>(只适用官方主题)</p>
  194. <select id="jsdelivrSelect">
  195. ${jsdelivrOptions.map(option => `<option value="${option}" ${option === jsdelivr ? 'selected' : ''}>${option}</option>`).join('')}
  196. ${`<option value="custom" ${!jsdelivrOptions.includes(jsdelivr) ? 'selected' : ''}>自定义</option>`}
  197. </select >
  198. <input type="text" id="customJsdelivr" placeholder="输入自定义地址" ${!jsdelivrOptions.includes(jsdelivr) ? `value='${jsdelivr}' style="display:block;" ` : `style="display:none;"`}>
  199. <button id="saveJsdelivr">保存 jsdelivr 设置</button>
  200.  
  201. <p>选择主题:</p>
  202. <select id="themeSelect">
  203. ${themeOptions.map(option => `<option value="${option}" ${option === theme ? 'selected' : ''}>${option}(${themeLabels[option]})(${themeTypes[option] == 'gif' ? '动图' : '静图'})</option>`).join('')}
  204. ${`<option value="custom" ${!themeOptions.includes(theme) ? 'selected' : ''}>自定义</option>`}
  205. </select>
  206.  
  207. <div id="customThemeInputs" style="display: ${!themeOptions.includes(theme) ? 'block' : 'none'};">
  208. ${statusKeys.map(key => `
  209. <input type="text" id="${key}" placeholder="输入 ${statusLabels[key]}" value="${localStorage.getItem(key) || getImage(theme, key)}">
  210. `).join('')}
  211. </div>
  212.  
  213. <button id="saveTheme">保存主题设置</button>
  214.  
  215. <p>替换右侧栏评测状态:</p>
  216. <select id="replaceSidebarStatus">
  217. <option value="1" ${replaceSidebarStatus === "1" ? "selected" : ""}>是</option>
  218. <option value="0" ${replaceSidebarStatus === "0" ? "selected" : ""}>否</option>
  219. </select>
  220. <button id="saveReplaceSidebarStatus">保存状态设置</button>
  221. `;
  222.  
  223. // 添加样式
  224. let style = document.createElement("style");
  225. style.innerHTML = `
  226. #settingsPopup {
  227. position: fixed;
  228. top: 50%;
  229. left: 50%;
  230. transform: translate(-50%, -50%);
  231. width: 300px;
  232. background: white;
  233. border: 1px solid #ccc;
  234. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
  235. padding: 20px;
  236. z-index: 9999;
  237. border-radius: 8px;
  238. text-align: center;
  239. font-family: Arial, sans-serif;
  240. max-height: 80vh; /* 限制最大高度为 80% 视口高度 */
  241. overflow-y: auto; /* 内容超出时可滚动 */
  242. }
  243.  
  244. #settingsPopup .popup-header {
  245. display: flex;
  246. justify-content: space-between;
  247. align-items: center;
  248. font-size: 16px;
  249. font-weight: bold;
  250. border-bottom: 1px solid #ccc;
  251. padding-bottom: 8px;
  252. margin-bottom: 10px;
  253. }
  254. #settingsPopup #closePopup {
  255. background: none;
  256. border: none;
  257. cursor: pointer;
  258. font-size: 18px;
  259. }
  260.  
  261. #settingsPopup input, select {
  262. width: 100%;
  263. padding: 5px;
  264. margin: 5px 0;
  265. border-radius: 5px;
  266. border: 1px solid #ccc;
  267. box-sizing: border-box;
  268. text-align: center;
  269. }
  270.  
  271. #settingsPopup button:not(.popup-header button) {
  272. background: #007bff;
  273. color: white;
  274. border: none;
  275. padding: 8px 12px;
  276. cursor: pointer;
  277. border-radius: 5px;
  278. width: 100%;
  279. }
  280.  
  281. #settingsPopup button:not(.popup-header button):hover {
  282. background: #0056b3;
  283. }
  284. `;
  285.  
  286. document.body.appendChild(style);
  287. document.body.appendChild(popup);
  288.  
  289. // 事件监听
  290. document.getElementById("saveOpacity").addEventListener("click", function () {
  291. let opacity = document.getElementById("opacityInput").value;
  292. if (opacity < 0 || opacity > 1 || opacity === "") {
  293. alert("请输入 0~1 之间的数");
  294. return;
  295. }
  296. localStorage.setItem("opacity", opacity);
  297. alert(`设置已保存:透明度 = ${opacity}`);
  298. });
  299. document.getElementById("jsdelivrSelect").addEventListener("change", function () {
  300. let customInput = document.getElementById("customJsdelivr");
  301. customInput.style.display = this.value === "custom" ? "block" : "none";
  302. });
  303.  
  304. document.getElementById("saveJsdelivr").addEventListener("click", function () {
  305. let selected = document.getElementById("jsdelivrSelect").value;
  306. let newJsdelivr = selected === "custom" ? document.getElementById("customJsdelivr").value : selected;
  307. if (!newJsdelivr) {
  308. alert("请输入有效的 jsdelivr 地址");
  309. return;
  310. }
  311. localStorage.setItem("jsdelivr", newJsdelivr);
  312. alert(`设置已保存:jsdelivr = ${newJsdelivr}`);
  313. });
  314.  
  315. document.getElementById("themeSelect").addEventListener("change", function () {
  316. document.getElementById("customThemeInputs").style.display = this.value === "custom" ? "block" : "none";
  317. });
  318.  
  319. document.getElementById("saveTheme").addEventListener("click", function () {
  320. let selected = document.getElementById("themeSelect").value;
  321. let newTheme = selected === "custom" ? "custom" : selected;
  322.  
  323. if (newTheme === "custom") {
  324. let missingFields = statusKeys.filter(key => !document.getElementById(key).value.trim());
  325. if (missingFields.length > 0) {
  326. alert("请填写所有图片的完整 URL!");
  327. return;
  328. }
  329.  
  330. statusKeys.forEach(key => localStorage.setItem(key, document.getElementById(key).value.trim()));
  331. }
  332.  
  333. localStorage.setItem("theme", newTheme);
  334. alert(`设置已保存:主题 = ${newTheme === "custom" ? "自定义" : newTheme + '(' + themeLabels[newTheme] + ')'}`);
  335. });
  336.  
  337. document.getElementById("saveReplaceSidebarStatus").addEventListener("click", function () {
  338. let value = document.getElementById("replaceSidebarStatus").value;
  339. localStorage.setItem("replaceSidebarStatus", value);
  340. alert(`设置已保存:${value === "1" ? "替换" : "不替换"} 右侧栏评测状态`);
  341. });
  342.  
  343. document.getElementById("closePopup").addEventListener("click", function () {
  344. document.body.removeChild(popup);
  345. });
  346. }
  347.  
  348. // 绑定点击事件,打开悬浮框
  349. let sidebar = document.querySelector(".nav-group.on-expand ul");
  350. if (!sidebar) return;
  351. sidebar.insertAdjacentHTML("beforeend", `
  352. <li data-v-40281d0d="" data-v-6c9e83f4="" title="插件设置">
  353. <a data-v-12b24cc3="" data-v-40281d0d="" href="#" class="" disabled="false" id="pluginSettingsBtn">
  354. <span data-v-40281d0d="" class="title minor">插件设置</span>
  355. </a>
  356. </li>
  357. `);
  358.  
  359. document.getElementById("pluginSettingsBtn").addEventListener("click", function (event) {
  360. event.preventDefault();
  361. createSettingsPopup();
  362. });
  363. }
  364. (function () {
  365. 'use strict';
  366. if (/^https:\/\/www\.luogu\.com\.cn\/record\/\d+$/.test(window.location.href)) {
  367. setInterval(function () {
  368. subBetter();
  369. }, 10);
  370. }
  371. setTimeout(function () {
  372. addButton();
  373. }, 1000);
  374. })();

QingJ © 2025

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