lowerCodeHelper

低代码平台助手

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

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