lowerCodeHelper

低代码平台助手

当前为 2023-12-08 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name lowerCodeHelper
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.3.1
  5. // @description 低代码平台助手
  6. // @author Ziker
  7. // @match https://ops.iyunquna.com/63008/*
  8. // @match http://localhost:63342/api/file/*
  9. // @require https://code.jquery.com/jquery-3.4.1.min.js
  10. // @icon https://favicon.qqsuu.cn/work.yqn.com
  11. // @grant GM_openInTab
  12. // @grant unsafeWindow
  13. // @grant window.close
  14. // @grant window.focus
  15. // @run-at document-body
  16. // @noframes
  17. // @license AGPL License
  18. // ==/UserScript==
  19.  
  20. window.jq = $.noConflict(true);
  21.  
  22. (function (window) {
  23. window.pageHelper = {
  24. // 等待元素可见
  25. waitElementVisible(visibleTag, index, fun) {
  26. let node = jq(visibleTag)
  27. if (node === null || node[index] === null || node[index] === undefined) {
  28. setTimeout(() => {
  29. pageHelper.waitElementVisible(visibleTag, index, fun)
  30. }, 500)
  31. } else {
  32. fun()
  33. }
  34. },
  35. getCurrentAppIndex() {
  36. const nodes = document.getElementsByClassName("bar-tab");
  37. for (let i = 0; i < nodes.length; i++) {
  38. if (nodes[i].className.indexOf("bar-tab-selected") > 0) {
  39. return i;
  40. }
  41. }
  42. return -1;
  43. },
  44. getCurrentApiId() {
  45. const nodes = document.getElementsByClassName("tab-content-presentation-components");
  46. if (nodes.length === 0) {
  47. return null;
  48. }
  49. let className = nodes[this.getCurrentAppIndex()].className;
  50. return className.substring(className.indexOf("theia-tab-") + "theia-tab-".length);
  51. },
  52. sleep(duration) {
  53. return new Promise(resolve => {
  54. setTimeout(resolve, duration)
  55. })
  56. },
  57. showToast(msg, duration) {
  58. // 显示提示
  59. duration = isNaN(duration) ? 3000 : duration;
  60. const m = document.createElement('div');
  61. m.innerHTML = msg;
  62. m.style.cssText = "display: flex;justify-content: center;align-items: center;width:60%; min-width:180px; " +
  63. "background:#000000; opacity:0.98; height:auto;min-height: 50px;font-size:25px; color:#fff; " +
  64. "line-height:30px; text-align:center; border-radius:4px; position:fixed; top:85%; left:20%; z-index:999999;";
  65. document.body.appendChild(m);
  66. setTimeout(function () {
  67. const d = 0.5;
  68. m.style.webkitTransition = '-webkit-transform ' + d + 's ease-in, opacity ' + d + 's ease-in';
  69. m.style.opacity = '0';
  70. setTimeout(function () {
  71. document.body.removeChild(m)
  72. }, d * 1000);
  73. }, duration);
  74. },
  75. // 关闭窗口
  76. closeWindow() {
  77. window.opener = null;
  78. window.open('', '_self')
  79. window.close()
  80. },
  81. formatString(str, len, padding) {
  82. const diff = len - str.toString().length;
  83. if (diff > 0) {
  84. return padding.repeat(diff) + str;
  85. } else {
  86. return str;
  87. }
  88. },
  89. initSetting() {
  90. const customSetting = document.createElement("div");
  91. document.body.appendChild(customSetting)
  92. customSetting.innerHTML = `
  93. <div id="copy-setting"
  94. style="display: none;position: absolute;top: 0;right: 0;background-color: #fff3f3;padding: 10px;width: 600px;z-index: 9999">
  95. <p>拷贝动作</p>
  96. <p><label>Copy File Id <input type="radio" name="copyValue" value="1" /></label></p>
  97. <p><label>Rest Api Open File <input type="radio" name="copyValue" value="2" checked/></label>&nbsp;&nbsp;&nbsp;较新版本IDEA需要下载 IDE Remote Control 插件</p>
  98. <p><label>ToolBox Open File <input type="radio" name="copyValue" value="3" /></label>&nbsp;&nbsp;&nbsp;需要下载 Jetbrains ToolBox 工具箱软件</p>
  99. <div id="projectPath" style="visibility: visible"><p><label><input name="path" style="width: 100%" type="text" placeholder="项目路径截止到 src 前 E:/yqnProject/yqn-wms/yqn-wms-rest-provider/"/></label></p></div>
  100. <p style="visibility: hidden"><label>同时打开编排IDE <input type="checkbox" name="openIDE" /></label>&nbsp;&nbsp;&nbsp;</p>
  101. <div style="display: flex;margin: 5px;justify-content: space-between">
  102. <button id="save" lang="zh" type="button" class="ant-btn ant-btn-default perf-tracked yqn-button"><span
  103. style="margin-left: 5px;">save</span></button>
  104. <button id="close" lang="zh" type="button" class="ant-btn ant-btn-default perf-tracked yqn-button"><span
  105. style="margin-left: 5px;">close</span></button>
  106. </div>
  107. </div>
  108. `
  109. let copyValue = localStorage.getItem("copyValue")
  110. copyValue = copyValue === null || copyValue === undefined ? 1 : copyValue
  111. document.querySelector('input[name="copyValue"][value="' + copyValue + '"]').checked = true
  112. document.getElementById("projectPath").style.visibility = copyValue === "2" ? "visible" : "hidden"
  113.  
  114. let path = localStorage.getItem("path")
  115. document.querySelector('input[name="path"]').value = path === null || path === undefined ? null : path
  116.  
  117. let openIDE = JSON.parse(localStorage.getItem("openIDE"));
  118. document.querySelector('input[name="openIDE"]').checked = openIDE === null || openIDE === undefined ? false : openIDE
  119.  
  120. const radios = document.querySelectorAll('input[name="copyValue"]');
  121. for (let i = 0; i < radios.length; i++) {
  122. radios[i].onchange = () => {
  123. const remoteRadio = document.querySelector('input[name="copyValue"]:checked');
  124. document.getElementById("projectPath").style.visibility = remoteRadio.value === "2" ? "visible" : "hidden";
  125. }
  126. }
  127. document.getElementById("save").addEventListener("click", () => {
  128. const remoteRadio = document.querySelector('input[name="copyValue"]:checked').value;
  129. const pathInput = document.querySelector('input[name="path"]').value;
  130. const openIDEValue = document.querySelector('input[name="openIDE"]').checked;
  131. if (remoteRadio === '2' && (pathInput === null || pathInput.length === 0)) {
  132. this.showToast("路径不可为空", 1000)
  133. } else {
  134. localStorage.setItem("copyValue", remoteRadio)
  135. localStorage.setItem("path", pathInput)
  136. localStorage.setItem("openIDE", JSON.stringify(openIDEValue))
  137. this.showToast("保存成功", 1000)
  138. }
  139. })
  140. document.getElementById("close").addEventListener("click", () => {
  141. document.getElementById("copy-setting").style.display = "none"
  142. })
  143. }
  144. }
  145. })(window);
  146.  
  147.  
  148. (function () {
  149. let historyTrace = ''
  150. 'use strict';
  151. let appName = null
  152. jq(document).ready(function () {
  153. if (window.location.href.indexOf("localhost:63342/api/file/") >= 0) {
  154. window.pageHelper.closeWindow()
  155. return
  156. }
  157. // 监听api tab变动
  158. waitObserve(".tabs-bar", () => {
  159. // 应用接口面板
  160. const appPanelTag = ".tab-content-presentation-components.theia-tab-" + window.pageHelper.getCurrentApiId();
  161. // 监听属性面板变动
  162. waitObserve(appPanelTag + " .p-8", () => {
  163. const p8 = document.querySelector(appPanelTag + " .p-8");
  164. const panel = p8.querySelector(".ant-form.ant-form-vertical.yqn-form");
  165. if (nonNull(panel) && isNull(panel.querySelector(".customScriptInfo"))) {
  166. const div = document.createElement("div");
  167. div.className = "customScriptInfo";
  168. panel.appendChild(div)
  169. // 获取当前NodeName
  170. const childProcessNode = panel.querySelector("#code");
  171. const codeSpan = p8.querySelector(".dashboard-code span");
  172. if (isNull(childProcessNode) && isNull(codeSpan)) {
  173. return
  174. }
  175. let nodeName = nonNull(childProcessNode) ? childProcessNode.value : codeSpan.textContent
  176. // 获取当前节点信息
  177. getNodeInfo(nodeName, node => {
  178. // 处理信息
  179. const divNode = document.querySelector(appPanelTag + " .customScriptInfo");
  180. // 清空显示
  181. divNode.innerHTML = ""
  182. // 脚本
  183. if (nonNull(node.scriptId)) {
  184. divNode.appendChild(createButton("脚本:" + node.scriptId, () => copyToClipboard(node.scriptId)))
  185. }
  186. // 入参
  187. let id = node.inputScriptId;
  188. const inputScriptIds = node.inputScriptIds;
  189. if (nonNull(inputScriptIds)) {
  190. id = nonNull(id) ? id : inputScriptIds.example;
  191. id = nonNull(id) ? id : inputScriptIds.record;
  192. id = nonNull(id) ? id : inputScriptIds.recordList;
  193. id = nonNull(id) ? id : inputScriptIds.condition;
  194. id = nonNull(id) ? id : inputScriptIds.id;
  195. }
  196. let isJava = true
  197. if (nonNull(node.dslBulidData)) {
  198. isJava = false
  199. }
  200. if (isNull(id) && nonNull(node.dslBulidData)) {
  201. id = node.dslBulidData.dslScriptId;
  202. }
  203. if (nonNull(id)) {
  204. divNode.appendChild(createButton("入参:" + id, () => copyToClipboard(id, isJava)))
  205. }
  206. // 条件
  207. const executeScriptId = node.executeScriptId;
  208. if (nonNull(executeScriptId)) {
  209. divNode.appendChild(createButton("条件:" + executeScriptId, () => copyToClipboard(executeScriptId)))
  210. }
  211. // 校验
  212. const assertScriptId = node.assertScriptId;
  213. if (nonNull(assertScriptId)) {
  214. divNode.appendChild(createButton("校验:" + assertScriptId, () => copyToClipboard(assertScriptId)))
  215. }
  216. })
  217. }
  218. })
  219.  
  220. // 监听执行历史面板变动
  221. const executeHistoryBodyTag = appPanelTag + " .test-split-item.test-split-item-right .ant-table-body tbody";
  222. waitObserve(executeHistoryBodyTag, () => {
  223. const lines = document.querySelectorAll(executeHistoryBodyTag + " .ant-table-row.ant-table-row-level-0");
  224. if (nonNull(lines) && isNull(lines[0].querySelector(".customer-button-div"))) {
  225. const div = document.createElement("div");
  226. lines[0].appendChild(div)
  227. div.className = "customer-button-div";
  228. div.style.display = "none"
  229. getExecuteHistory(content => {
  230. for (let i = 0; i < content.length; i++) {
  231. const tds = lines[i].querySelectorAll("td");
  232. const actionCol = tds[tds.length - 1];
  233. const detailButton = actionCol.querySelectorAll("button")[0];
  234.  
  235. detailButton.style.display = "none"
  236. actionCol.insertBefore(createButton("RT:" + content[i].rt, () => {
  237. detailButton.click()
  238. historyTrace = tds[3].innerText
  239. }), detailButton)
  240. }
  241. })
  242. }
  243. })
  244. })
  245.  
  246. // 监听body变动
  247. waitObserve("body", () => {
  248. // 监听执行日志流程图变动
  249. const processPanelTag = ".node-bpmn #canvas .bjs-container .djs-container .viewport .layer-base";
  250. waitObserve(processPanelTag, () => {
  251. const processPanel = document.querySelector(processPanelTag);
  252. const rtDiv = processPanel.querySelector(".customer-rt");
  253. if (isNull(rtDiv)) {
  254. const div = document.createElement("div");
  255. processPanel.appendChild(div)
  256. div.className = "customer-rt";
  257. div.style.display = "none"
  258. getByTraceId(apiNodeLogList => {
  259. for (let i = 0; i < apiNodeLogList.length; i++) {
  260. const log = apiNodeLogList[i];
  261. const textNode = processPanel.querySelector("[data-element-id='" + log.nodeId + "'] text");
  262. if (isNull(textNode)) {
  263. continue
  264. }
  265. const tspan = textNode.querySelector("tspan");
  266. if (nonNull(tspan)) {
  267. const rtNode = tspan.cloneNode(true);
  268. textNode.appendChild(rtNode)
  269. rtNode.setAttribute("x", "65")
  270. rtNode.setAttribute("y", "15")
  271. rtNode.innerHTML = window.pageHelper.formatString(log.rt, 4, '&nbsp;&nbsp;')
  272. }
  273. }
  274. })
  275. }
  276. }, false)
  277. })
  278.  
  279. // 工具栏设置按钮
  280. waitObserve(".app-actions", () => {
  281. const buttonLists = document.querySelector(".app-actions");
  282. if (isNull(buttonLists) || nonNull(document.querySelector(".setting-flag"))) {
  283. return
  284. }
  285. appendFlagNode(document.body, "setting-flag")
  286. const settingButton = document.createElement("div");
  287. settingButton.style.marginLeft = '10px'
  288. const button = document.createElement("button")
  289. button.type = "button"
  290. button.id = name
  291. button.className = "ant-btn ant-btn-default perf-tracked yqn-button"
  292. button.onclick = () => {
  293. const settingPanel = document.getElementById("copy-setting");
  294. settingPanel.style.display = settingPanel.style.display === 'block' ? 'none' : "block"
  295. }
  296. const span = document.createElement("span")
  297. span.textContent = "CopySetting"
  298. button.appendChild(span)
  299. settingButton.appendChild(button)
  300. buttonLists.appendChild(settingButton)
  301. })
  302.  
  303. window.pageHelper.initSetting()
  304. getAppProjectName(data => appName = data)
  305. })
  306.  
  307.  
  308. function nonNull(o) {
  309. return o !== null && o !== undefined;
  310. }
  311.  
  312. function isNull(o) {
  313. return o === null || o === undefined;
  314. }
  315.  
  316.  
  317. // 追加标记节点
  318. function appendFlagNode(node, flag) {
  319. const divFlag = document.createElement("div");
  320. node.appendChild(divFlag)
  321. divFlag.className = flag;
  322. divFlag.style.display = "none"
  323. }
  324.  
  325. // 等待出现并监听变化
  326. function waitObserve(visibleTag, fun, attributes = true) {
  327. window.pageHelper.waitElementVisible(visibleTag, 0, () => {
  328. new MutationObserver(function (mutationsList) {
  329. fun()
  330. }).observe(document.querySelector(visibleTag), {
  331. attributes: attributes,
  332. childList: true,
  333. subtree: true,
  334. characterData: true
  335. })
  336. })
  337. }
  338.  
  339. function copyToClipboard(text, isJava = true) {
  340. let copyValue = localStorage.getItem("copyValue");
  341. copyValue = copyValue === null || copyValue === undefined ? '1' : copyValue;
  342. if (copyValue === '1') {
  343. let textarea = document.createElement('textarea');
  344. textarea.value = text;
  345. document.body.appendChild(textarea);
  346. textarea.select();
  347. document.execCommand('copy');
  348. document.body.removeChild(textarea);
  349. window.pageHelper.showToast("已拷贝 " + text, 1500)
  350. } else if (copyValue === '2') {
  351. let path = localStorage.getItem("path");
  352. path = path === null || path === undefined ? null : path;
  353. otherReq("http://127.0.0.1:63342/api/file/" + path + "src/main/java/com/yqn/framework/composer/api/api_" + window.pageHelper.getCurrentApiId() + "/script/Script_" + text + (isJava ? ".java" : ".json"))
  354. window.pageHelper.showToast("已打开文件,如未打开,检查插件是否安装以及path是否正确", 2000)
  355. } else if (copyValue === '3') {
  356. window.open('jetbrains://idea/navigate/reference?project=' + appName + '&fqn=com.yqn.framework.composer.api.api_' + window.pageHelper.getCurrentApiId() + '.script.Script_' + text)
  357. window.pageHelper.showToast("已打开文件,如未打开,请确认已安装Jetbrains Toolbox ", 2000)
  358. }
  359. }
  360.  
  361. // 创建按钮
  362. function createButton(name, listener) {
  363. const button = document.createElement("button")
  364. button.type = "button"
  365. button.id = name
  366. button.className = "ant-btn ant-btn-link perf-tracked yqn-button yqn-link-no-padding customer-button"
  367. button.onclick = listener
  368. const span = document.createElement("span")
  369. span.textContent = name
  370. button.appendChild(span)
  371. return button;
  372. }
  373.  
  374. // 拿流程信息
  375. function getNodeInfo(nodeName, fuc, apiMode = 0) {
  376. const addressArr = ['', '/process', '/mq', '/job'];
  377. remoteReq('/api/42080/api' + addressArr[apiMode] + '/details_composer', {
  378. "id": window.pageHelper.getCurrentApiId(),
  379. }, data => {
  380. if (isNull(data)) {
  381. if (apiMode === 0) {
  382. getNodeInfo(nodeName, fuc, 1)
  383. getNodeInfo(nodeName, fuc, 2)
  384. getNodeInfo(nodeName, fuc, 3)
  385. }
  386. return
  387. }
  388. let processDefine = JSON.parse(data.processDefine);
  389. for (let i = 0; i < processDefine.nodes.length; i++) {
  390. if (processDefine.nodes[i].code === nodeName) {
  391. fuc(processDefine.nodes[i])
  392. break
  393. }
  394. }
  395. })
  396. }
  397.  
  398. // 拿历史执行数据
  399. function getExecuteHistory(fuc) {
  400. remoteReq('/api/42086/apiLog/list', {}, data => {
  401. if (nonNull(data) && nonNull(data.content)) {
  402. fuc(data.content)
  403. }
  404. })
  405. }
  406.  
  407. // 拿 trace 执行数据
  408. function getByTraceId(fuc, apiMode = 0) {
  409. const apiType = ['api', 'process', 'mq', 'job'];
  410. remoteReq('/api/42086/apiLog/getByTraceId', {
  411. "traceIdLike": historyTrace,
  412. "testCaseId": null,
  413. "apiTypeCode": apiMode === 0 ? "api" : apiType[apiMode]
  414. }, data => {
  415. if (nonNull(data) && nonNull(data.apiNodeLogList) && data.apiNodeLogList.length !== 0) {
  416. fuc(data.apiNodeLogList)
  417. } else if (apiMode === 0) {
  418. getByTraceId(fuc, 1)
  419. getByTraceId(fuc, 2)
  420. getByTraceId(fuc, 3)
  421. }
  422. })
  423. }
  424.  
  425. // 获取应用名称
  426. function getAppProjectName(fuc) {
  427. remoteReq('/api/42080/application/getById', {}, data => {
  428. if (nonNull(data)) {
  429. fuc(data.appName)
  430. }
  431. })
  432. }
  433.  
  434.  
  435. function remoteReq(url, model, fuc) {
  436. model.apiId = window.pageHelper.getCurrentApiId()
  437. model.appId = new URLSearchParams(window.location.href.split('?')[1]).get('appId')
  438. model.env = "qa4"
  439. model.page = 1
  440. model.size = 20
  441. jq.ajax({
  442. url: 'https://gw-ops.iyunquna.com' + url,
  443. method: 'POST',
  444. xhrFields: {
  445. withCredentials: true
  446. },
  447. crossDomain: true,
  448. contentType: 'application/json',
  449. data: JSON.stringify({
  450. "header": {
  451. "xSourceAppId": "63008",
  452. "guid": "6f87e073-1da1-4017-b2de-c109abcd6d123",
  453. "lang": "zh",
  454. "timezone": "Asia/Shanghai"
  455. },
  456. "model": model
  457. }),
  458. success: function (response) {
  459. if (response.code === 200) {
  460. fuc(response.data)
  461. }
  462. },
  463. error: function (xhr, status, error) {
  464. console.log('Request failed:', error);
  465. }
  466. });
  467. }
  468.  
  469.  
  470. function otherReq(url) {
  471. jq.ajax(url, {
  472. method: "GET",
  473. xhrFields: {
  474. withCredentials: true
  475. },
  476. crossDomain: true
  477. })
  478. }
  479. })();
  480.