TC dev enhance

2023/9/19 17:48:17

当前为 2023-09-27 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name TC dev enhance
  3. // @namespace Violentmonkey Scripts
  4. // @homepageURL https://github.com/kikyous/tc-dev-enhance
  5. // @match http://localhost:5000/*
  6. // @match https://lms-stg.tronclass.com.cn/*
  7. // @match https://lms-qa.tronclass.com.cn/*
  8. // @match https://lms-product.tronclass.com.cn/*
  9. // @grant none
  10. // @noframes
  11. // @version 2.1
  12. // @author chen
  13. // @description 2023/9/19 17:48:17
  14. // ==/UserScript==
  15.  
  16.  
  17.  
  18. const logout = () => {
  19. return fetch("/api/logout", {
  20. headers: { "content-type": "application/json;charset=UTF-8" },
  21. method: "POST",
  22. }).catch(() => true);
  23. };
  24.  
  25. const login = (username, password, orgId = '', credentials = 'same-origin') => {
  26. const data = {
  27. user_name: username,
  28. password: password,
  29. remember: true,
  30. };
  31. if (orgId) {
  32. data.org_id = orgId;
  33. }
  34. return fetch("/api/login", {
  35. headers: { "content-type": "application/json;charset=UTF-8" },
  36. credentials: credentials,
  37. body: JSON.stringify(data),
  38. method: "POST",
  39. }).then(async function (response) {
  40. if (!response.ok) {
  41. throw (await response.json());
  42. }
  43. return response.json();
  44. });
  45. };
  46.  
  47.  
  48.  
  49.  
  50. const style = `
  51. *, *:before, *:after {
  52. box-sizing: border-box;
  53. }
  54. :host {
  55. font-size: 13px;
  56. }
  57.  
  58. .user-input {
  59. color: red;
  60. border: 1px solid #cbcbcb;
  61. border-radius: 2px;
  62. outline: none;
  63. padding: 1px 4px;
  64. width: 100%;
  65. background: rgba(0, 0, 0, 0);
  66. }
  67.  
  68. form {
  69. margin: 0
  70. }
  71.  
  72. .orgs-wrapper label {
  73. margin: 8px 0;
  74. line-height: 1;
  75. display: flex;
  76. align-items: center;
  77. }
  78.  
  79. .orgs-wrapper input {
  80. margin: 0 5px;
  81. }
  82.  
  83. .orgs-wrapper {
  84. background: white;
  85. border: 1px solid #cbcbcb;
  86. padding: 0 5px;
  87. border-radius: 2px;
  88. }
  89.  
  90. .info {
  91. background: white;
  92. border-radius: 2px;
  93. border: 1px solid #cbcbcb;
  94. padding: 3px 10px;
  95. }
  96.  
  97. :host(.error) .user-input, :host(.error) .user-input:focus {
  98. border: red solid 2px;
  99. }
  100.  
  101. .slide-in-top {
  102. animation: slide-in-top 0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) both;
  103. }
  104.  
  105. @keyframes slide-in-top {
  106. 0% {
  107. -webkit-transform: translateY(-1000px);
  108. transform: translateY(-1000px);
  109. opacity: 0;
  110. }
  111. 100% {
  112. -webkit-transform: translateY(0);
  113. transform: translateY(0);
  114. opacity: 1;
  115. }
  116. }
  117.  
  118. @keyframes linearGradientMove {
  119. 100% {
  120. background-position: 4px 0, -4px 100%, 0 -4px, 100% 4px;
  121. }
  122. }
  123.  
  124. :host(.loading) .user-input {
  125. background:
  126. linear-gradient(90deg, red 50%, transparent 0) repeat-x,
  127. linear-gradient(90deg, red 50%, transparent 0) repeat-x,
  128. linear-gradient(0deg, red 50%, transparent 0) repeat-y,
  129. linear-gradient(0deg, red 50%, transparent 0) repeat-y;
  130. background-size: 4px 1px, 4px 1px, 1px 4px, 1px 4px;
  131. background-position: 0 0, 0 100%, 0 0, 100% 0;
  132. animation: linearGradientMove .3s infinite linear;
  133. }
  134. `;
  135.  
  136.  
  137. customElements.define('enhance-input', class extends HTMLElement {
  138. constructor() {
  139. super();
  140. const value = window.globalData?.user?.userNo || window.statisticsSettings?.user?.userNo || ''
  141.  
  142. const shadowRoot = this.attachShadow({ mode: 'open' });
  143. shadowRoot.innerHTML = `
  144. <style>${style}</style>
  145. <form class='slide-in-top'>
  146. <input class='user-input' name='user_name' autocomplete="on" onfocus="this.select()" value="${value}" type=text />
  147. </form>
  148. `;
  149.  
  150. const form = shadowRoot.querySelector("form");
  151. const input = shadowRoot.querySelector("input");
  152.  
  153. form.addEventListener("submit", (event) => {
  154. event.preventDefault()
  155. this.submit(input.value)
  156. })
  157. }
  158.  
  159. showInfo(message) {
  160. this.shadowRoot.querySelector('.info')?.remove();
  161. const div = document.createElement("div");
  162. div.classList.add('info', 'slide-in-top')
  163. this.shadowRoot.appendChild(div);
  164. div.innerText = message
  165. setTimeout(() => {
  166. div.remove()
  167. }, 5000);
  168. }
  169.  
  170. selectOrgIfNeeded(result) {
  171. const form = this.shadowRoot.querySelector('form');
  172. this.shadowRoot.querySelector('.orgs-wrapper')?.remove()
  173.  
  174. return new Promise((resolve) => {
  175. const orgs = result['orgs']
  176. if (orgs) {
  177. const orgsHtml = orgs.map((org) => {
  178. return `<label><input type="radio" name="org" value="${org.id}"> <span> ${org.name} </span> </label>`
  179. }).join('')
  180. form.insertAdjacentHTML('beforeend', `<div class="orgs-wrapper slide-in-top">${orgsHtml}</div>`)
  181. form.querySelectorAll('.orgs-wrapper input[type="radio"]').forEach((radio) => {
  182. radio.addEventListener('change', (event) => {
  183. resolve({ org_id: event.target.value })
  184. })
  185. })
  186. } else {
  187. resolve(result)
  188. }
  189. })
  190. }
  191.  
  192. submit(value) {
  193. if (!value) {
  194. return
  195. }
  196.  
  197. let [username, password] = value.split(' ')
  198. if (!password) {
  199. password = localStorage.getItem('__pswd') || 'password'
  200. }
  201. const logoutAndLogin = (result) => {
  202. return logout()
  203. .then(() => login(username, password, result['org_id']))
  204. .then(() => {
  205. window.location.reload();
  206. })
  207. }
  208.  
  209. this.shadowRoot.querySelector('.orgs-wrapper')?.remove()
  210. this.classList.remove('error');
  211. this.classList.add('loading');
  212.  
  213.  
  214. const testLogin = () => {
  215. return login(username, password, '', 'omit').then((result) => {
  216. localStorage.setItem('__pswd', password);
  217. return result
  218. }).catch((result) => {
  219. const errors = result.errors || {};
  220. this.showInfo(errors['user_name']?.at(0) || errors['password']?.at(0));
  221. throw new Error('login failed');
  222. })
  223. }
  224.  
  225. // test account then logout
  226. testLogin().then(this.selectOrgIfNeeded.bind(this)).then(logoutAndLogin).catch(() => {
  227. this.classList.add("error");
  228. this.classList.remove('loading');
  229. })
  230. }
  231. });
  232.  
  233. const inject = (node) => {
  234. node.insertAdjacentHTML(
  235. "afterbegin",
  236. `<enhance-input style='width: 120px; position: fixed; top: 0; right: 0; z-index: 9999;'></enhance-input>`
  237. );
  238. };
  239.  
  240. inject(document.body);