bsn-utilities

工具箱

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.cn-greasyfork.org/scripts/486039/1486205/bsn-utilities.js

  1. // 分组
  2. function groupBy(arr, predicate) {
  3. const obj = arr.reduce((acc, obj) => {
  4. const key = predicate(obj) ?? ''
  5. if (!acc[key]) {
  6. acc[key] = []
  7. }
  8. acc[key].push(obj)
  9. return acc
  10. }, {})
  11. return Object.keys(obj).map(x => ({
  12. key: x,
  13. items: obj[x]
  14. }))
  15. }
  16. // 随眠
  17. function sleep(time) {
  18. return new Promise(resolve => setTimeout(resolve, time))
  19. }
  20. // 修改输入框的值(模拟键盘输入)
  21. function setInputValue(input, value) {
  22. input.value = value;
  23. input.dispatchEvent(new InputEvent('input'));
  24. }
  25. // 获取粘贴板文字
  26. async function getClipboardText() {
  27. if (navigator.clipboard && navigator.clipboard.readText) {
  28. const text = await navigator.clipboard.readText();
  29. return text;
  30. }
  31. return "";
  32. }
  33. // 在HTML_ELEMENT中查找所有满足条件的元素,参数innerText可以是字符串(判断innerText是否相等)或REG
  34. function findAllIn(ele, selectors, innerText) {
  35. const arr = Array.from(ele.querySelectorAll(selectors));
  36. return innerText === undefined
  37. ? arr
  38. : typeof innerText === "string"
  39. ? arr.filter(x => x.innerText.trim() === innerText.trim())
  40. : arr.filter(x => x.innerText.trim().match(innerText));
  41. }
  42. // 在HTML_ELEMENT中查找第一个满足条件的元素,参数innerText可以是字符串(判断innerText是否相等)或REG
  43. function findIn(ele, selectors, innerText) {
  44. const arr = findAllIn(ele, selectors, innerText);
  45. return arr.length > 0 ? arr[0] : null;
  46. }
  47. // 在HTML_ELEMENT中查找最后一个满足条件的元素,参数innerText可以是字符串(判断innerText是否相等)或REG
  48. function findLastIn(ele, selectors, innerText) {
  49. const arr = findAllIn(ele, selectors, innerText);
  50. return arr.length > 0 ? arr[arr.length - 1] : null;
  51. }
  52. // 在document中查找所有满足条件的元素,参数innerText可以是字符串(判断innerText是否相等)或REG
  53. function findAll(selectors, innerText) {
  54. return findAllIn(document, selectors, innerText);
  55. }
  56. // 在document中查找第一个满足条件的元素,参数innerText可以是字符串(判断innerText是否相等)或REG
  57. function find(selectors, innerText) {
  58. return findIn(document, selectors, innerText);
  59. }
  60. // 在document中查找最后一个满足条件的元素,参数innerText可以是字符串(判断innerText是否相等)或REG
  61. function findLast(selectors, innerText) {
  62. return findLastIn(document, selectors, innerText);
  63. }
  64. // 选择下拉选项,input为下拉选项元素,wait为等待时间,optionParentSelectors为选项父元素的选择器,optionSelectors为选项的选择器,matchFunc为匹配函数(满足条件后触发点击操作)
  65. async function selectOptionIn(input, wait, optionParentSelectors, optionSelectors, matchFunc) {
  66. input.click();
  67. await sleep(wait);
  68. const optionParent = optionParentSelectors ? Array.from(findAll(optionParentSelectors)).find(x => x.style.display !== 'none') : document;
  69. const optionEles = findAllIn(optionParent, optionSelectors);
  70. const option = optionEles.find((x, i) => matchFunc(x.innerText, i));
  71. if (option) {
  72. option.click();
  73. }
  74. }
  75. // 选择下拉选项,input为下拉选项元素,wait为等待时间,optionSelectors为选项的选择器,matchFunc为匹配函数(满足条件后触发点击操作)
  76. async function selectOption(input, wait, optionSelectors, matchFunc) {
  77. await selectOptionIn(input, wait, null, optionSelectors, matchFunc);
  78. }
  79. // 创建naive对话框,增加异步功能,只能在组件的setup函数里调用
  80. function createNaiveDialog() {
  81. const dialog = naive.useDialog();
  82. ["create", "error", "info", "success", "warning"].forEach(x => {
  83. dialog[x + "Async"] = options => {
  84. return new Promise((resolve,reject) => {
  85. dialog[x]({
  86. ...options,
  87. onNegativeClick: () => resolve(false),
  88. onPositiveClick: () => resolve(true)
  89. });
  90. });
  91. }
  92. });
  93. return dialog;
  94. }
  95. // 初始化Vue3,包括naive及自定义BTable组件
  96. function initVue3(Com) {
  97. const style = document.createElement('style');
  98. style.type = 'text/css';
  99. style.innerHTML=`
  100. .app-wrapper .btn-toggle {
  101. position: fixed;
  102. top: 50vh;
  103. right: 0;
  104. padding-left: 12px;
  105. padding-bottom: 4px;
  106. transform: translateX(calc(100% - 32px)) translateY(-50%);
  107. }
  108. .drawer-wrapper .n-form {
  109. margin: 0 8px;
  110. }
  111. .drawer-wrapper .n-form .n-form-item {
  112. margin: 8px 0;
  113. }
  114. .drawer-wrapper .n-form .n-form-item .n-space {
  115. flex: 1;
  116. }
  117. .drawer-wrapper .n-form .n-form-item .n-input-number {
  118. width: 100%;
  119. }
  120. `;
  121. document.getElementsByTagName('head').item(0).appendChild(style);
  122. const el = document.createElement("div");
  123. el.innerHTML = `
  124. <div id="app" class="app-wrapper"></div>`;
  125. document.body.append(el);
  126.  
  127. const BTable = {
  128. template: `
  129. <table cellspacing="0" cellpadding="0">
  130. <tr v-for="(row, rowIndex) in rows">
  131. <td v-for="cell in row" :rowspan="cell.rowspan" :colspan="cell.colspan" :width="cell.width" :class="cell.class">
  132. <slot :cell="cell">{{cell.value}}</slot>
  133. </td>
  134. </tr>
  135. </table>
  136. `,
  137. props: {
  138. rowCount: Number,
  139. columns: Array, // [{ key: "", label: "", width: "100px", unit: "", editable: false }]
  140. cells: Array // [{ row: 0, col: 0, rowspan: 1, colspan: 1, value: "", useColumnLabel: false }]
  141. },
  142. setup(props) {
  143. const data = Vue.reactive({
  144. rows: Vue.computed(() => {
  145. const arr1 = [];
  146. for(let i = 0; i < props.rowCount; i++) {
  147. const arr2 = [];
  148. for (let j = 0; j < props.columns.length; j++) {
  149. const column = props.columns[j];
  150. const cell = props.cells.find(x => x.row === i && x.col === j);
  151. if (cell) {
  152. const colspan = cell.colspan ?? 1;
  153. arr2.push({
  154. ...cell,
  155. rowspan: cell.rowspan ?? 1,
  156. colspan: colspan,
  157. value: cell.useColumnLabel ? column.label : cell.value,
  158. width: colspan > 1 ? undefined : column.width,
  159. column: column
  160. });
  161. }
  162. }
  163. arr1.push(arr2);
  164. }
  165. return arr1;
  166. })
  167. });
  168. return data;
  169. }
  170. }
  171. const app = Vue.createApp({
  172. template: `
  173. <n-dialog-provider>
  174. <n-message-provider>
  175. <n-button class="btn-toggle" type="primary" round @click="showDrawer=true">
  176. <template #icon>⇆</template>
  177. </n-button>
  178. <n-drawer v-model:show="showDrawer" display-directive="show" resizable class="drawer-wrapper">
  179. <com @closeDrawer="showDrawer=false"/>
  180. </n-drawer>
  181. </n-message-provider>
  182. </n-dialog-provider>
  183. `,
  184. setup() {
  185. const data = Vue.reactive({
  186. showDrawer: false
  187. });
  188. return data;
  189. }
  190. });
  191. app.use(naive);
  192. app.component('b-table', BTable);
  193. app.component('com', Com);
  194. app.mount("#app");
  195. }