Greasy Fork

bsn-libs

工具箱

目前为 2024-12-08 提交的版本。查看 最新版本

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

/** 分组 */
window.groupBy = function (arr, predicate) {
  const obj = arr.reduce((acc, obj) => {
    const key = predicate(obj) ?? ''
    if (!acc[key]) {
      acc[key] = []
    }
    acc[key].push(obj)
    return acc
  }, {})
  return Object.keys(obj).map(x => ({
    key: x,
    items: obj[x]
  }))
}

/** 睡眠 */
window.sleep = function (time) {
  return new Promise(resolve => setTimeout(resolve, time))
}

/** 获取粘贴板文字 */
window.getClipboardText = async function () {
  if (navigator.clipboard && navigator.clipboard.readText) {
    const text = await navigator.clipboard.readText()
    return text
  }
  return ''
}

/** 查找所有满足条件的元素 */
window.findAll = function (options) {
  const { selectors, parent, findTarget } = options
  const eles = Array.from((parent ?? document.body).querySelectorAll(selectors))
  return findTarget ? eles.map((el, index) => findTarget(el, index)).filter(x => x) : eles
}

/** 查找第一个满足条件的元素 */
window.find = function (options) {
  const eles = window.findAll(options)
  return eles.length > 0 ? eles[0] : null
}

/** 查找最后一个满足条件的元素 */
window.findLast = function (options) {
  const eles = window.findAll(options)
  return eles.length > 0 ? eles[eles.length - 1] : null
}

/** 模拟操作 */
window.simulateOperate = async function (actions) {
  for (const action of actions) {
    switch (action.type) {
      case 'sleep':
        await sleep(action.time)
        break
      case 'input':
      case 'click':
        const { selectors, value, parent, findTarget } = action
        const eles = Array.from((parent ?? document.body).querySelectorAll(selectors))
        const targets = findTarget
          ? eles.map((el, index) => findTarget(el, index)).filter(x => x)
          : eles
        if (targets.length > 0) {
          const target = targets[0]
          if (action.type === 'input') {
            target.value = value
            target.dispatchEvent(new InputEvent('input'))
          } else {
            target.click()
          }
        }
        break
    }
  }
}

/** 创建naive对话框(增加异步功能且只能在组件的setup函数里调用) */
window.createNaiveDialog = function () {
  const dialog = naive.useDialog()
  ;['create', 'error', 'info', 'success', 'warning'].forEach(x => {
    dialog[x + 'Async'] = options => {
      return new Promise(resolve => {
        dialog[x]({
          ...options,
          onNegativeClick: () => resolve(false),
          onPositiveClick: () => resolve(true)
        })
      })
    }
  })
  return dialog
}

/** 初始化Vue3(包括naive及自定义BTable组件) */
window.initVue3 = function (Com) {
  const style = document.createElement('style')
  style.type = 'text/css'
  style.innerHTML = `
  .app-wrapper .btn-toggle {
    position: fixed;
    top: 50vh;
    right: 0;
    padding-left: 12px;
    padding-bottom: 4px;
    transform: translateX(calc(100% - 32px)) translateY(-50%);
  }
  .drawer-wrapper .n-form {
    margin: 0 8px;
  }
  .drawer-wrapper .n-form .n-form-item {
    margin: 8px 0;
  }
  .drawer-wrapper .n-form .n-form-item .n-space {
    flex: 1;
  }
  .drawer-wrapper .n-form .n-form-item .n-input-number {
    width: 100%;
  }
    `
  document.getElementsByTagName('head').item(0).appendChild(style)
  const el = document.createElement('div')
  el.innerHTML = `<div id="app" class="app-wrapper"></div>`
  document.body.append(el)

  const BTable = {
    template: `
    <table cellspacing="0" cellpadding="0">
      <tr v-for="(row, rowIndex) in rows">
        <td v-for="cell in row" :rowspan="cell.rowspan" :colspan="cell.colspan" :width="cell.width" :class="cell.class">
          <slot :cell="cell">{{cell.value}}</slot>
        </td>
      </tr>
    </table>
      `,
    props: {
      rowCount: Number,
      columns: Array, // [{ key: "", label: "", width: "100px", unit: "", editable: false }]
      cells: Array // [{ row: 0, col: 0, rowspan: 1, colspan: 1, value: "", useColumnLabel: false }]
    },
    setup(props) {
      const data = Vue.reactive({
        rows: Vue.computed(() => {
          const arr1 = []
          for (let i = 0; i < props.rowCount; i++) {
            const arr2 = []
            for (let j = 0; j < props.columns.length; j++) {
              const column = props.columns[j]
              const cell = props.cells.find(x => x.row === i && x.col === j)
              if (cell) {
                const colspan = cell.colspan ?? 1
                arr2.push({
                  ...cell,
                  rowspan: cell.rowspan ?? 1,
                  colspan: colspan,
                  value: cell.useColumnLabel ? column.label : cell.value,
                  width: colspan > 1 ? undefined : column.width,
                  column: column
                })
              }
            }
            arr1.push(arr2)
          }
          return arr1
        })
      })
      return data
    }
  }
  const app = Vue.createApp({
    template: `
<n-dialog-provider>
  <n-message-provider>
    <n-button class="btn-toggle" type="primary" round @click="showDrawer=true">
      <template #icon>⇆</template>
    </n-button>
  <n-drawer v-model:show="showDrawer" display-directive="show" resizable class="drawer-wrapper">
    <com @closeDrawer="showDrawer=false"/>
  </n-drawer>
  </n-message-provider>
</n-dialog-provider>
`,
    setup() {
      const data = Vue.reactive({
        showDrawer: false
      })
      return data
    }
  })
  app.use(naive)
  app.component('b-table', BTable)
  app.component('com', Com)
  app.mount('#app')
}