lowerCodeHelper

低代码平台助手

当前为 2024-01-15 提交的版本,查看 最新版本

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