您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在简道云表单后台快速切换同应用下的不同表单。支持中英文首字母排序和分组。
// ==UserScript== // @name 简道云表单后台快速切表器 // @namespace zerobiubiu.top // @version 1.2 // @description 在简道云表单后台快速切换同应用下的不同表单。支持中英文首字母排序和分组。 // @author zerobiubiu // @match https://www.jiandaoyun.com/dashboard/app/*/form/*/edit // @license MIT // @icon chrome-extension://jpejneelbjckppjapemgfeheifljmaib/_favicon/?pageUrl=https%3A%2F%2Fwww.jiandaoyun.com%2Fdashboard%23%2F&size=32 // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/index.js // @grant GM_xmlhttpRequest // @grant GM_listValues // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue // @grant GM_registerMenuCommand // @run-at document-idle // ==/UserScript== (async function () { 'use strict'; // 获取公司信息 async function fetchCorpInfo() { const csrf = document.querySelector('meta[name="csrf-token"]').content; const requestId = crypto.randomUUID(); const resp = await fetch("https://www.jiandaoyun.com/profile/get_corp", { method: "POST", credentials: "include", headers: { "accept": "application/json, text/plain, */*", "content-type": "application/json", "x-csrf-token": csrf, "x-request-id": requestId, }, body: "{}" }); return resp.json(); } // 启动密钥检查 async function getSecret(KEY, NAME) { let secret = await GM_getValue(KEY); if (!secret) { secret = prompt('请输入密钥(只需输入一次,后续会自动复用):'); if (secret) { await GM_setValue(KEY, secret); await GM_setValue(KEY + "-companyName", NAME); } } return secret; } // 从 URL 中提取 app_id function getAppId() { const match = location.href.match(/\/app\/([^/]+)\/form\//); return match ? match[1] : null; } // 从 URL 提取 entry_id function getEntryId() { const match = location.href.match(/\/form\/([^/]+)/); return match ? match[1] : null; } // 请求 API 获取所有表单(自动翻页) async function fetchAllForms(appId, secret) { const allForms = []; let skip = 0; const limit = 100; while (true) { const chunk = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: "POST", url: "https://api.jiandaoyun.com/api/v5/app/entry/list", headers: { "Content-Type": "application/json", "Authorization": "Bearer " + secret }, data: JSON.stringify({ app_id: appId, limit, skip }), onload: function (res) { try { const data = JSON.parse(res.responseText); resolve(data.forms || []); } catch (e) { reject("解析失败: " + e); } }, onerror: function (err) { reject("请求失败: " + err); } }); }); if (chunk.length === 0) break; // 没有更多了 allForms.push(...chunk); skip += limit; } return allForms; } // 菜单注册标记 let menuCreated = false; // 创建菜单 function createMenu(KEY, NAME) { if (menuCreated) return; menuCreated = true; GM_registerMenuCommand('重置密钥(设置密钥)', async () => { const secret = prompt('请输入新的密钥:'); if (!secret) return; await GM_setValue(KEY, secret); await GM_setValue(KEY + "-companyName", NAME); alert('密钥已更新!'); }); GM_registerMenuCommand("删除当前企业密钥", () => { if (confirm("⚠️ 确认要删除当前企业密钥吗?此操作不可恢复!")) { GM_deleteValue(KEY); GM_deleteValue(KEY + "-companyName"); alert("✅ 密钥已删除!"); } else { alert("❎ 已取消删除操作"); } }); GM_registerMenuCommand("查看所有key(控制台输出)", async () => { console.log(await GM_listValues()); }); GM_registerMenuCommand("查看当前企业密钥", async () => { const secret = await GM_getValue(KEY); if (secret) { alert(NAME + " 当前密钥为:" + secret); } else { alert("当前未设置密钥!"); } }); GM_registerMenuCommand("清空所有密钥(慎点!!)", async () => { if (confirm("⚠️ 确认要清空所有 密钥 数据吗?此操作不可恢复!")) { const keys = await GM_listValues(); for (const key of keys) { await GM_deleteValue(key); console.log("已删除:", key); } alert("✅ 所有 密钥 数据已清空"); } else { alert("❎ 已取消清空操作"); } }); } // 提取当前表单ID const currentEntryId = getEntryId(); // 创建下拉框 const select = document.createElement('select'); select.id = 'formSelect'; select.style.marginLeft = '10px'; select.style.padding = '2px 6px'; // 创建选择事件 select.addEventListener("change", () => { const entryId = select.value; if (!entryId) return; const newUrl = window.location.href.replace( /(\/form\/)([^/]+)(\/)/, `$1${entryId}$3` ); window.location.href = newUrl; }); // 监听执行锁 let executed = false; // 创建监听事件 const observer = new MutationObserver(async (mutationsList, observer) => { const navigation_left = document.querySelector("#root > div > div.fx-navigation-bar.fx-form-navigation-bar > div.navigation-left"); if (navigation_left && !document.getElementById("formSelect") && !executed) { executed = true; // 上锁,防止重复执行 observer.disconnect(); // 找到后立即停止监听,提高性能 // 获取当前企业ID作为KEY_ID const { KEY, NAME } = await fetchCorpInfo().then(data => { console.log("当前企业ID:" + data.corp_id) return { KEY: data.corp_id, NAME: data.corp_name } }); createMenu(KEY, NAME); const secret = await getSecret(KEY, NAME); if (!secret) return; // 如果没有获取到密钥则停止执行 const forms = await fetchAllForms(getAppId(), secret); // 统一的获取首字母/分组的工具函数 function getGroupKey(str) { if (!str || !str.trim()) return '#'; // 处理空名称 const firstChar = str.trim().charAt(0); if (/[a-zA-Z]/.test(firstChar)) return firstChar.toUpperCase(); if (/[0-9]/.test(firstChar)) return '0-9'; try { const pinyinResult = pinyinPro.pinyin(firstChar, { pattern: 'first', toneType: 'none' }); const letter = pinyinResult ? pinyinResult.toUpperCase() : '#'; // 确保pinyin-pro的结果是单个字母 return /^[A-Z]$/.test(letter) ? letter : '#'; } catch (e) { console.error("pinyin-pro 库运行出错:", e); return '#'; } } // 1. 排序表单:主排序按分组键,次排序按完整名称 forms.sort((a, b) => { const nameA = a.name || ''; const nameB = b.name || ''; const groupA = getGroupKey(nameA); const groupB = getGroupKey(nameB); if (groupA < groupB) return -1; if (groupA > groupB) return 1; // numeric: true 选项可以正确处理 "表单1", "表单10", "表单2" 这样的数字排序 return nameA.localeCompare(nameB, 'zh-Hans-CN', { numeric: true }); }); // 2. 构造分组 const groups = {}; const groupOrder = []; forms.forEach(f => { const name = f.name || ''; const groupKey = getGroupKey(name); if (!groups[groupKey]) { groups[groupKey] = []; groupOrder.push(groupKey); } groups[groupKey].push(f); }); // 3. 对分组键本身进行排序:字母 -> 数字 -> 其他 groupOrder.sort((a, b) => { const isLetterA = /^[A-Z]$/.test(a); const isLetterB = /^[A-Z]$/.test(b); const isNumA = a === '0-9'; const isNumB = b === '0-9'; if (isLetterA && !isLetterB) return -1; // 字母优先 if (!isLetterA && isLetterB) return 1; if (isLetterA && isLetterB) return a.localeCompare(b); // 字母内按A-Z排序 if (isNumA && !isNumB) return -1; // 数字其次 if (!isNumA && isNumB) return 1; return a.localeCompare(b); // 其他符号按字符排序 }); // 4. 渲染分组到下拉框 select.innerHTML = ''; // 清空旧内容 groupOrder.forEach(key => { if (groups[key] && groups[key].length > 0) { const optgroup = document.createElement('optgroup'); optgroup.label = key; groups[key].forEach(f => { const opt = document.createElement('option'); opt.value = f.entry_id; opt.textContent = f.name; if (f.entry_id === currentEntryId) { opt.selected = true; } optgroup.appendChild(opt); }); select.appendChild(optgroup); } }); // 挂载表单选择器 navigation_left.appendChild(select); } }); // 启动 observer observer.observe(document.querySelector("#root"), { childList: true, subtree: true }); })();