สคริปต์นี้ไม่ควรถูกติดตั้งโดยตรง มันเป็นคลังสำหรับสคริปต์อื่น ๆ เพื่อบรรจุด้วยคำสั่งเมทา // @require https://update.greasyfork.org/scripts/486039/1486205/bsn-utilities.js
// 分组
function groupBy(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]
}))
}
// 随眠
function sleep(time) {
return new Promise(resolve => setTimeout(resolve, time))
}
// 修改输入框的值(模拟键盘输入)
function setInputValue(input, value) {
input.value = value;
input.dispatchEvent(new InputEvent('input'));
}
// 获取粘贴板文字
async function getClipboardText() {
if (navigator.clipboard && navigator.clipboard.readText) {
const text = await navigator.clipboard.readText();
return text;
}
return "";
}
// 在HTML_ELEMENT中查找所有满足条件的元素,参数innerText可以是字符串(判断innerText是否相等)或REG
function findAllIn(ele, selectors, innerText) {
const arr = Array.from(ele.querySelectorAll(selectors));
return innerText === undefined
? arr
: typeof innerText === "string"
? arr.filter(x => x.innerText.trim() === innerText.trim())
: arr.filter(x => x.innerText.trim().match(innerText));
}
// 在HTML_ELEMENT中查找第一个满足条件的元素,参数innerText可以是字符串(判断innerText是否相等)或REG
function findIn(ele, selectors, innerText) {
const arr = findAllIn(ele, selectors, innerText);
return arr.length > 0 ? arr[0] : null;
}
// 在HTML_ELEMENT中查找最后一个满足条件的元素,参数innerText可以是字符串(判断innerText是否相等)或REG
function findLastIn(ele, selectors, innerText) {
const arr = findAllIn(ele, selectors, innerText);
return arr.length > 0 ? arr[arr.length - 1] : null;
}
// 在document中查找所有满足条件的元素,参数innerText可以是字符串(判断innerText是否相等)或REG
function findAll(selectors, innerText) {
return findAllIn(document, selectors, innerText);
}
// 在document中查找第一个满足条件的元素,参数innerText可以是字符串(判断innerText是否相等)或REG
function find(selectors, innerText) {
return findIn(document, selectors, innerText);
}
// 在document中查找最后一个满足条件的元素,参数innerText可以是字符串(判断innerText是否相等)或REG
function findLast(selectors, innerText) {
return findLastIn(document, selectors, innerText);
}
// 选择下拉选项,input为下拉选项元素,wait为等待时间,optionParentSelectors为选项父元素的选择器,optionSelectors为选项的选择器,matchFunc为匹配函数(满足条件后触发点击操作)
async function selectOptionIn(input, wait, optionParentSelectors, optionSelectors, matchFunc) {
input.click();
await sleep(wait);
const optionParent = optionParentSelectors ? Array.from(findAll(optionParentSelectors)).find(x => x.style.display !== 'none') : document;
const optionEles = findAllIn(optionParent, optionSelectors);
const option = optionEles.find((x, i) => matchFunc(x.innerText, i));
if (option) {
option.click();
}
}
// 选择下拉选项,input为下拉选项元素,wait为等待时间,optionSelectors为选项的选择器,matchFunc为匹配函数(满足条件后触发点击操作)
async function selectOption(input, wait, optionSelectors, matchFunc) {
await selectOptionIn(input, wait, null, optionSelectors, matchFunc);
}
// 创建naive对话框,增加异步功能,只能在组件的setup函数里调用
function createNaiveDialog() {
const dialog = naive.useDialog();
["create", "error", "info", "success", "warning"].forEach(x => {
dialog[x + "Async"] = options => {
return new Promise((resolve,reject) => {
dialog[x]({
...options,
onNegativeClick: () => resolve(false),
onPositiveClick: () => resolve(true)
});
});
}
});
return dialog;
}
// 初始化Vue3,包括naive及自定义BTable组件
function initVue3(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");
}