bsn-libs

工具箱

This script should not be not be installed directly. It is a library for other scripts to include with the meta directive // @require https://update.greatest.deepsurf.us/scripts/520145/1544039/bsn-libs.js

  1. /** 获取本地存储 */
  2. window.getLocalStorage = function (key, defaultValue) {
  3. return lscache.get(key) ?? defaultValue;
  4. };
  5.  
  6. /** 设置本地存储 */
  7. window.setLocalStorage = function (key, value) {
  8. lscache.set(key, value);
  9. };
  10.  
  11. /** 睡眠 */
  12. window.sleep = function (time) {
  13. return new Promise(resolve => setTimeout(resolve, time));
  14. };
  15.  
  16. /** 获取粘贴板文字 */
  17. window.getClipboardText = async function () {
  18. if (navigator.clipboard && navigator.clipboard.readText) {
  19. const text = await navigator.clipboard.readText();
  20. return text;
  21. }
  22. return '';
  23. };
  24.  
  25. /** 设置粘贴板文字 */
  26. window.setClipboardText = async function (data) {
  27. if (navigator.clipboard && navigator.clipboard.writeText) {
  28. await navigator.clipboard.writeText(data);
  29. }
  30. };
  31.  
  32. /** 查找所有满足条件的元素 */
  33. window.findAll = function (options) {
  34. const { selectors, parent, findTargets } = options;
  35. const parentEl =
  36. parent && parent.tagName.toLocaleLowerCase() === 'iframe'
  37. ? parent.contentDocument.body
  38. : parent;
  39. const eles = Array.from((parentEl ?? document.body).querySelectorAll(selectors));
  40. return findTargets ? findTargets(eles) : eles;
  41. };
  42.  
  43. /** 查找第一个满足条件的元素 */
  44. window.find = function (options) {
  45. const eles = window.findAll(options);
  46. return eles.length > 0 ? eles[0] : null;
  47. };
  48.  
  49. /** 查找最后一个满足条件的元素 */
  50. window.findLast = function (options) {
  51. const eles = window.findAll(options);
  52. return eles.length > 0 ? eles[eles.length - 1] : null;
  53. };
  54.  
  55. /** 模拟点击 */
  56. window.simulateClick = function (element, options) {
  57. if (options?.original) {
  58. element.click();
  59. } else {
  60. ['mousedown', 'click', 'mouseup'].forEach(mouseEventType =>
  61. element.dispatchEvent(
  62. new MouseEvent(mouseEventType, {
  63. bubbles: true,
  64. cancelable: true,
  65. buttons: 1
  66. })
  67. )
  68. );
  69. }
  70. };
  71.  
  72. /** 模拟输入 */
  73. window.simulateInput = function (element, val, options) {
  74. if (options?.focusable === undefined || options?.focusable) element.focus();
  75. const lastValue = element.value;
  76. element.value = val;
  77. if (options.original) {
  78. element.dispatchEvent(new Event('keydown'));
  79. element.dispatchEvent(new Event('keypress'));
  80. element.dispatchEvent(new Event('input'));
  81. element.dispatchEvent(new Event('keyup'));
  82. element.dispatchEvent(new Event('change'));
  83. } else {
  84. const event = new Event('input', {
  85. bubbles: true
  86. });
  87. let keyPress = new KeyboardEvent('keyup', {
  88. bubbles: true,
  89. key: 'enter'
  90. });
  91. // hack React15
  92. event.simulated = true;
  93. // hack React16
  94. const tracker = element._valueTracker;
  95. if (tracker) {
  96. tracker.setValue(lastValue);
  97. }
  98. element.dispatchEvent(event);
  99. element.dispatchEvent(keyPress);
  100. }
  101. };
  102.  
  103. /**
  104. * 模拟操作
  105. * actions: [
  106. * {
  107. * type: 'sleep',
  108. * time: 1000
  109. * },
  110. * {
  111. * type: 'focus',
  112. * selectors: '',
  113. * parent?: HTMLELEMENT,
  114. * findTarget?: els => undefined,
  115. * waiting?: 1000,
  116. * nextSteps?: []
  117. * },
  118. * {
  119. * type: 'input',
  120. * selectors: '',
  121. * value: '',
  122. * parent?: HTMLELEMENT,
  123. * findTarget?: els => undefined,
  124. * waiting?: 1000,
  125. * focusable?: true,
  126. * original?: true,
  127. * nextSteps?: []
  128. * },
  129. * {
  130. * type: 'click',
  131. * selectors: '',
  132. * parent?: HTMLELEMENT,
  133. * findTarget?: els => undefined,
  134. * waiting?: 1000,
  135. * focusable?: true,
  136. * original?: true,
  137. * nextSteps?: []
  138. * }
  139. * ]
  140. */
  141. window.simulateOperate = async function (actions) {
  142. for (const action of actions) {
  143. if (action.type === 'sleep') {
  144. await sleep(action.time);
  145. } else {
  146. const { selectors, parent, waiting, findTarget, nextSteps } = action;
  147. if (waiting) await sleep(waiting);
  148. const parentEl =
  149. parent && parent.tagName.toLocaleLowerCase() === 'iframe'
  150. ? parent.contentDocument.body
  151. : parent;
  152. const eles = Array.from((parentEl ?? document.body).querySelectorAll(selectors));
  153. if (eles.length === 0) continue;
  154. const target = findTarget ? findTarget(eles) : eles[0];
  155. if (!target) continue;
  156. switch (action.type) {
  157. case 'focus':
  158. target.focus();
  159. break;
  160. case 'input':
  161. window.simulateInput(target, action.value, {
  162. original: action.original,
  163. focusable: action.focusable
  164. });
  165. break;
  166. case 'click':
  167. window.simulateClick(target, {
  168. original: action.original
  169. });
  170. break;
  171. }
  172. if (nextSteps && nextSteps.length > 0) {
  173. await window.simulateOperate(nextSteps);
  174. }
  175. }
  176. }
  177. };
  178.  
  179. /** 创建naive对话框(增加异步功能且只能在组件的setup函数里调用) */
  180. window.createNaiveDialog = function () {
  181. const dialog = naive.useDialog();
  182. ['create', 'error', 'info', 'success', 'warning'].forEach(x => {
  183. dialog[x + 'Async'] = options => {
  184. return new Promise(resolve => {
  185. dialog[x]({
  186. ...options,
  187. onNegativeClick: () => resolve(false),
  188. onPositiveClick: () => resolve(true)
  189. });
  190. });
  191. };
  192. });
  193. return dialog;
  194. };
  195.  
  196. /** 初始化Vue3(包括naive及自定义BTable组件) */
  197. window.initVue3 = function (com) {
  198. const style = document.createElement('style');
  199. style.type = 'text/css';
  200. style.innerHTML = `
  201. body {
  202. text-align: left;
  203. }
  204. .app-wrapper .btn-toggle {
  205. position: fixed;
  206. top: 50vh;
  207. right: 0;
  208. padding-left: 12px;
  209. padding-bottom: 4px;
  210. transform: translateX(calc(100% - 32px)) translateY(-50%);
  211. }
  212. .drawer-wrapper .n-form {
  213. margin: 0 8px;
  214. }
  215. .drawer-wrapper .n-form .n-form-item {
  216. margin: 8px 0;
  217. }
  218. .drawer-wrapper .n-form .n-form-item .n-space {
  219. flex: 1;
  220. }
  221. .drawer-wrapper .n-form .n-form-item .n-input-number {
  222. width: 100%;
  223. }
  224. `;
  225. document.getElementsByTagName('head').item(0).appendChild(style);
  226. const el = document.createElement('div');
  227. el.innerHTML = `<div id="app" class="app-wrapper"></div>`;
  228. el.style.backgroundColor = 'transparent';
  229. el.style.border = 'none';
  230. document.body.append(el);
  231. el.popover = 'manual';
  232. el.showPopover();
  233.  
  234. const BTable = {
  235. template: `
  236. <table cellspacing="0" cellpadding="0">
  237. <tr v-for="(row, rowIndex) in rows">
  238. <td v-for="cell in row" :rowspan="cell.rowspan" :colspan="cell.colspan" :width="cell.width" :class="cell.class">
  239. <slot :cell="cell">{{cell.value}}</slot>
  240. </td>
  241. </tr>
  242. </table>
  243. `,
  244. props: {
  245. rowCount: Number,
  246. columns: Array, // [{ key: "", label: "", width: "100px", unit: "", editable: false }]
  247. cells: Array // [{ row: 0, col: 0, rowspan: 1, colspan: 1, value: "", useColumnLabel: false }]
  248. },
  249. setup(props) {
  250. const data = Vue.reactive({
  251. rows: Vue.computed(() => {
  252. const arr1 = [];
  253. for (let i = 0; i < props.rowCount; i++) {
  254. const arr2 = [];
  255. for (let j = 0; j < props.columns.length; j++) {
  256. const column = props.columns[j];
  257. const cell = props.cells.find(x => x.row === i && x.col === j);
  258. if (cell) {
  259. const colspan = cell.colspan ?? 1;
  260. arr2.push({
  261. ...cell,
  262. rowspan: cell.rowspan ?? 1,
  263. colspan: colspan,
  264. value: cell.useColumnLabel ? column.label : cell.value,
  265. width: colspan > 1 ? undefined : column.width,
  266. column: column
  267. });
  268. }
  269. }
  270. arr1.push(arr2);
  271. }
  272. return arr1;
  273. })
  274. });
  275. return data;
  276. }
  277. };
  278. const app = Vue.createApp({
  279. template: `
  280. <n-dialog-provider>
  281. <n-message-provider>
  282. <n-button v-if="!showDrawer" class="btn-toggle" type="primary" round @click="showDrawer=true">
  283. <template #icon>⇆</template>
  284. </n-button>
  285. <n-drawer v-model:show="showDrawer" display-directive="show" resizable class="drawer-wrapper">
  286. <com @closeDrawer="showDrawer=false"/>
  287. </n-drawer>
  288. </n-message-provider>
  289. </n-dialog-provider>
  290. `,
  291. setup() {
  292. const data = Vue.reactive({
  293. showDrawer: false
  294. });
  295. return data;
  296. }
  297. });
  298. app.use(naive);
  299. app.component('b-table', BTable);
  300. app.component('com', com);
  301. app.mount('#app');
  302. };
  303.  
  304. //#region 扩展
  305. Object.typedAssign = Object.assign;
  306. Object.typedKeys = Object.keys;
  307. Object.toArray = obj => {
  308. const keys = Object.keys(obj);
  309. return keys.map(x => ({ key: x, value: obj[x] }));
  310. };
  311. Object.deepClone = function (target) {
  312. if (typeof target !== 'object' || target === null) return target;
  313. if (target instanceof Date) {
  314. return new Date(target.getTime());
  315. }
  316. if (target instanceof RegExp) {
  317. return new RegExp(target);
  318. }
  319. if (target instanceof Array) {
  320. return target.map(x => Object.deepClone(x));
  321. }
  322. // 对象
  323. const newObj = Object.create(
  324. Reflect.getPrototypeOf(target),
  325. Object.getOwnPropertyDescriptors(target)
  326. );
  327. Reflect.ownKeys(target).forEach(key => {
  328. newObj[key] = Object.deepClone(target[key]);
  329. });
  330. return newObj;
  331. };
  332.  
  333. const compare = (item1, item2) =>
  334. typeof item1 === 'string' && typeof item2 === 'string'
  335. ? item1.localeCompare(item2, 'zh')
  336. : item1 > item2
  337. ? 1
  338. : item2 > item1
  339. ? -1
  340. : 0;
  341. Array.prototype.flatTreeNode = function () {
  342. const arr = [];
  343. for (const node of this) {
  344. arr.push(node);
  345. if (node.children instanceof Array) {
  346. arr.push(...node.children.flatTreeNode());
  347. }
  348. }
  349. return arr;
  350. };
  351. Array.prototype.traverseTreeNode = function (callback) {
  352. for (const node of this) {
  353. callback(node);
  354. if (node.children instanceof Array) {
  355. node.children.traverseTreeNode(callback);
  356. }
  357. }
  358. };
  359. Array.prototype.findTreeNodePath = function (match) {
  360. for (const node of this) {
  361. if (match(node)) {
  362. return [node];
  363. }
  364. if (node.children instanceof Array) {
  365. const result = node.children.findTreeNodePath(match);
  366. if (result) {
  367. return [node, ...result];
  368. }
  369. }
  370. }
  371. return undefined;
  372. };
  373. Array.prototype.localSort = function () {
  374. return this.sort(compare);
  375. };
  376. Array.prototype.sortBy = function (predicate) {
  377. return this.sort((a, b) => compare(predicate(a), predicate(b)));
  378. };
  379. Array.prototype.sortByDescending = function (predicate) {
  380. return this.sort((a, b) => -compare(predicate(a), predicate(b)));
  381. };
  382. Array.prototype.orderBy = function (predicate) {
  383. return [...this].sort((a, b) => compare(predicate(a), predicate(b)));
  384. };
  385. Array.prototype.orderByDescending = function (predicate) {
  386. return [...this].sort((a, b) => -compare(predicate(a), predicate(b)));
  387. };
  388. Array.prototype.orderByMany = function (predicates) {
  389. return [...this].sort((a, b) => {
  390. for (const predicate of predicates) {
  391. const result = compare(predicate(a), predicate(b));
  392. if (result) {
  393. return result;
  394. }
  395. }
  396. return 0;
  397. });
  398. };
  399. Array.prototype.orderByManyDescending = function (predicates) {
  400. return [...this].sort((a, b) => {
  401. for (const predicate of predicates) {
  402. const result = -compare(predicate(a), predicate(b));
  403. if (result) {
  404. return result;
  405. }
  406. }
  407. return 0;
  408. });
  409. };
  410. Array.prototype.first = function (predicate) {
  411. const arr = predicate === undefined ? this : this.filter(predicate);
  412. return arr[0];
  413. };
  414. Array.prototype.firstOrDefault = function (predicate) {
  415. const arr = predicate === undefined ? this : this.filter(predicate);
  416. return arr.length === 0 ? undefined : arr[0];
  417. };
  418. Array.prototype.groupBy = function (predicate) {
  419. const obj = this.reduce((acc, obj) => {
  420. const key = predicate(obj) ?? '';
  421. if (!acc[key]) {
  422. acc[key] = [];
  423. }
  424. acc[key].push(obj);
  425. return acc;
  426. }, {});
  427. return Object.typedKeys(obj).map(x => ({
  428. key: x,
  429. items: obj[x]
  430. }));
  431. };
  432.  
  433. Array.prototype.clear = function () {
  434. this.length = 0;
  435. };
  436. Array.prototype.remove = function (item) {
  437. for (let i = this.length - 1; i >= 0; i--) {
  438. if (this[i] === item) {
  439. this.splice(i, 1);
  440. }
  441. }
  442. };
  443. Array.prototype.removeRange = function (items) {
  444. for (let i = this.length - 1; i >= 0; i--) {
  445. if (items.indexOf(this[i]) >= 0) {
  446. this.splice(i, 1);
  447. }
  448. }
  449. };
  450. Array.prototype.unique = function () {
  451. const hash = [];
  452. for (let i = 0; i < this.length; i++) {
  453. let isOk = true;
  454. for (let j = 0; j < i; j++) {
  455. if (this[i] === this[j]) {
  456. isOk = false;
  457. break;
  458. }
  459. }
  460. if (isOk) {
  461. hash.push(this[i]);
  462. }
  463. }
  464. return hash;
  465. };
  466. Array.prototype.sum = function (deep) {
  467. let total = 0;
  468. for (const item of this) {
  469. if (typeof item === 'number') {
  470. total += item;
  471. } else if (deep && item instanceof Array) {
  472. total += item.sum(true);
  473. }
  474. }
  475. return total;
  476. };
  477. Array.prototype.average = function () {
  478. let total = 0;
  479. let k = 0;
  480. for (const item of this) {
  481. if (typeof item === 'number') {
  482. total += item;
  483. k++;
  484. }
  485. }
  486. return k === 0 ? undefined : total / k;
  487. };
  488. Array.prototype.swap = function (oldIndex, newIndex) {
  489. this.splice(newIndex, 0, this.splice(oldIndex, 1)[0]);
  490. return this;
  491. };
  492. Array.prototype.max = function (predicate) {
  493. return this.length === 0
  494. ? undefined
  495. : predicate === undefined
  496. ? this.reduce((max, current) => {
  497. return current > max ? current : max;
  498. })
  499. : this.reduce((max, current) => {
  500. return predicate(current) > predicate(max) ? current : max;
  501. });
  502. };
  503. Array.prototype.min = function (predicate) {
  504. return this.length === 0
  505. ? undefined
  506. : predicate === undefined
  507. ? this.reduce((min, current) => {
  508. return current < min ? current : min;
  509. })
  510. : this.reduce((min, current) => {
  511. return predicate(current) < predicate(min) ? current : min;
  512. });
  513. };
  514. Array.prototype.mapObject = function (mapKey, mapValue) {
  515. return Object.fromEntries(this.map((el, i, arr) => [mapKey(el, i, arr), mapValue(el, i, arr)]));
  516. };
  517.  
  518. Number.prototype.angleToRadian = function () {
  519. const value = Number(this);
  520. return (value * Math.PI) / 180;
  521. };
  522. Number.prototype.radianToAngle = function () {
  523. const value = Number(this);
  524. return (180 * value) / Math.PI;
  525. };
  526. Number.prototype.fillZero = function (length) {
  527. const value = Number(this);
  528. return Number.isInteger(value) && value.toString().length < length
  529. ? ('0'.repeat(length) + value).slice(-length)
  530. : value.toString();
  531. };
  532. Number.prototype.toThousands = function (unit, withSpaceBetween = true, prepend) {
  533. return this.toString().toThousands(unit, withSpaceBetween, prepend);
  534. };
  535. Number.prototype.toPercentage = function (fractionDigits) {
  536. const value = Number(this);
  537. return `${(value * 100).toFixed(fractionDigits)}%`;
  538. };
  539. Number.prototype.simplify = function (chinese, fractionDigits = 2) {
  540. const value = Number(this);
  541. const units = chinese ? ['万', '亿'] : ['million', 'billion'];
  542. const divisors = chinese ? [10_000, 100_000_000] : [1_000_000, 1_000_000_000];
  543. const index = value < divisors[1] ? 0 : 1;
  544. const num = (value / divisors[index]).toFixed(fractionDigits);
  545. const result = Number(num);
  546. return result === 0 ? '0' : result + ' ' + units[index];
  547. };
  548. Number.prototype.accurate = function (precision = 2) {
  549. const value = Number(this);
  550. if (precision >= 0) {
  551. return Number(value.toFixed(precision));
  552. }
  553. const num = Math.pow(10, precision);
  554. return Number((value * num).toFixed(0)) / num;
  555. };
  556. Number.prototype.toPageCount = function (pageSize) {
  557. const value = Number(this);
  558. return Math.floor(Math.abs(value - 1) / pageSize) + 1;
  559. };
  560.  
  561. String.prototype.replaceAll = function (find, replace) {
  562. return this.replace(new RegExp(find, 'g'), replace);
  563. };
  564. String.prototype.equals = function (value, ignoreCase = true) {
  565. const txt = value ?? '';
  566. return ignoreCase ? this.toLowerCase() === txt.toLowerCase() : this === txt;
  567. };
  568. String.prototype.toThousands = function (unit, withSpaceBetween = true, prepend) {
  569. const value = this;
  570. const index = value.indexOf('.');
  571. const firstPart = index >= 0 ? value.substring(0, index) : value;
  572. const lastPart = index >= 0 ? value.substring(index) : '';
  573. const result = firstPart.replace(/\B(?=(\d{3})+(?!\d))/g, ',') + lastPart;
  574. return unit
  575. ? prepend
  576. ? withSpaceBetween
  577. ? `${unit} ${result}`
  578. : unit + result
  579. : withSpaceBetween
  580. ? `${result} ${unit}`
  581. : result + unit
  582. : result;
  583. };
  584. //#endregion