JSON Viewer

格式化显示JSON使数据看起来更加漂亮,支持折叠/展开格式化后的数据,支持JSON脑图让调用层级看着更清晰,支持复制JSON脑图节点路径

当前为 2024-10-06 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @license MIT
  3. // @name JSON Viewer
  4. // @namespace http://tampermonkey.net/
  5. // @version 0.4.9
  6. // @note v0.4.9 布局修改,增加保存JSON/脑图为文件,增加JSON过滤,鼠标移入key提示json-path
  7. // @note v0.4.8 代码优化
  8. // @note v0.4.7 增加对JSONP的判断,代码优化
  9. // @note v0.4.6 增加复制按钮,JSON脑图CSS样式细节优化,JSON脑图增加收起/展开子节点按钮
  10. // @note v0.4.5 在json-viewer-updated原基础上进行了一些修改,主要有CSS样式修改,新增折叠/展开全部功能,新增JSON脑图功能,脑图节点点击显示调用路径
  11. // @description 格式化显示JSON使数据看起来更加漂亮,支持折叠/展开格式化后的数据,支持JSON脑图让调用层级看着更清晰,支持复制JSON脑图节点路径
  12. // @author Feny
  13. // @match *://*/*
  14. // @grant GM_addStyle
  15. // @grant GM_getValue
  16. // @grant GM_setValue
  17. // @grant unsafeWindow
  18. // @grant GM_setClipboard
  19. // @grant GM_getResourceText
  20. // @icon data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAeAB4AAD/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAEAAAAAAAD/2wBDAAIBAQIBAQICAgICAgICAwUDAwMDAwYEBAMFBwYHBwcGBwcICQsJCAgKCAcHCg0KCgsMDAwMBwkODw0MDgsMDAz/2wBDAQICAgMDAwYDAwYMCAcIDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAz/wAARCAAgACADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD9wvjF8bLX4ZrHZx+XNqlwnmKjH5YUyRvb6kEAd8H058d1b47XV5Ir3eoN++bagaXYrN6KOBn2FfPPx5/aEutX8a+KNWhIubhruZLSNj8u1WMcIOP4QoXOOwNS/wDBM79lDTfjh8YNe+IXj63TxRdeHfKjsE1KMTxtdSbmMmxvlAiVV2IBsUybgAyKR9ZTy6lhsM69Xovm2+iPmKmOqYjEKjT6v5WXU9e8beKrjxZpp/s/xFrfhjWIwWs9X0q42z2kmPlZo2zDcxjqYZ0eNv7obDDf/YL/AG8L745eJNc+G3xCttP0n4qeEWZZ2sVZNP8AENsu0rd2ysS0ZKPG7RMSQsisCcukWd+3x4RtfhpqGk+JNPjS0g1mV7a8iT5UM4XesgHYsofdjglQepJPxwPE1x4V/au8LePtNkeK40ma0lmdDgyorvHMhPo9uxjP+y1bUcHRxmGbS1adn1TXT0/4fcipiqmExCi3pfVdGn19f+GLni/w7ceGf2wPFXga+Vo5rW/vDbI3WWI/v4HA/wBqBg3tk+lfWX7AXia1+F/iDV9B1CRbWHXjFLayyHannpuUxk+rqy4zxlMdWAPQft7fsL33x91nQfiD4DutP0r4oeDXVrT7cWWx1y3UsTZ3LKCyAh5FEigkLLIpHzBk5vwj4JuvE2kLJfeHdY8N6lGAl5pepwBZrOT+JRIuYp0ByBNCzxPg4bIICqYyljMKot62Sa6prr6P/gBDC1MLiHJLS90+jT6ev/Dnmn/BVP8Aaw0nxv4/8P8Aw18KXK69qmk3MlxqEdiRMwuivlpbrtPLopkMnZNy5IIYLyPwa/Z8vvEmv+HtHu4/Ovr+5iS6KDcqAtukwe6om7nuEJxX0XoH7PK/bJG0vRoY5rr/AFslvbLGZf8AfcAf+PGvafgv8CbX4byNqFyI5tWmXYCoytsh6qvqx7t+A4yWiWYUsLhlRpbr72318kVHA1MTiHVqbP7kl0P/2Q==
  21. // @require https://code.jquery.com/jquery.min.js
  22. // @require https://unpkg.com/jsmind@0.8.5/es6/jsmind.js
  23. // @require https://unpkg.com/layer-src@3.5.1/dist/layer.js
  24. // @require https://unpkg.com/dom-to-image@2.6.0/dist/dom-to-image.min.js
  25. // @require https://unpkg.com/jsmind@0.8.5/es6/jsmind.screenshot.js
  26. // @resource swalStyle https://unpkg.com/jsmind@0.8.5/style/jsmind.css
  27. // @resource layerStyle https://unpkg.com/layer-src@3.5.1/dist/theme/default/layer.css
  28. // ==/UserScript==
  29.  
  30.  
  31. (function() {
  32. 'use strict';
  33.  
  34. const Utils = {
  35. // 检查字符串是否为URL
  36. isUrl: function (string) {
  37. var regexp = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/;
  38. return regexp.test(string);
  39. },
  40.  
  41. // 检查是否是图片链接
  42. isImg: function (pathImg) {
  43. // var regexp = /^(http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?\/([\w#!:.?+=&%@!\-\/])*\.(gif|jpg|jpeg|png|GIF|JPG|PNG)([\w#!:.?+=&%@!\-\/])?/;
  44. let regexp = /\.(ico|bmp|gif|jpg|jpeg|png|svg|webp|GIF|JPG|PNG|WEBP|SVG)([\w#!:.?+=&%@!\-\/])?/i
  45. return regexp.test(pathImg);
  46. },
  47. // 检验内容是否是json格式的内容
  48. isJSON: function (str) {
  49. try {
  50. JSON.parse(str)
  51. return true
  52. } catch(e) {
  53. console.log("is not json", e)
  54. return false
  55. }
  56. },
  57. // 获取数据类型
  58. getType: function (value){
  59. return Object.prototype.toString.call(value).match(/\s(.+)]/)[1].toLowerCase();
  60. },
  61. // JSON 过滤
  62. filterJson: function(json, filter) {
  63. if(!filter){
  64. return json
  65. }
  66.  
  67. filter = filter.toLowerCase()
  68. let newJSON = Utils.getType(json) == 'array' ? [] : {}
  69. for (let key in json) {
  70. let val = json[key]
  71. if (typeof val === 'object') {
  72. let subJSON = this.filterJson(val, filter);
  73. if (Object.keys(subJSON).length > 0) {
  74. newJSON[key] = subJSON;
  75. }
  76. }else{
  77. if(key.toLowerCase().includes(filter.toLowerCase())){
  78. newJSON[key] = val
  79. }
  80.  
  81. if(val !== null && val.toString().toLowerCase().includes(filter.toLowerCase())){
  82. newJSON[key] = val
  83. }
  84. }
  85. }
  86. return newJSON;
  87. }
  88. }
  89.  
  90. // jquery.json-viewer 插件 开始
  91. // 解决和原网页jquery版本冲突
  92. var _jQuery = jQuery.noConflict(true);
  93. (function($){
  94. /**
  95. * 检查 arg 是否为至少包含 1 个元素的数组或至少包含 1 个键的字典
  96. */
  97. function isCollapsable(arg) {
  98. return arg instanceof Object && Object.keys(arg).length > 0;
  99. }
  100.  
  101. /**
  102. * 将 JSON 对象转换为 HTML 表示形式
  103. * @return string
  104. */
  105. function json2html(json, parentPath = '') {
  106. let html = '', type = Utils.getType(json)
  107. switch(type){
  108. case 'array':
  109. case 'object':
  110. let len = json.length || Object.keys(json).length;
  111. if (len > 0) {
  112. html += '<span class="json-brackets">';
  113. html += type === 'array' ? '[</span><ol class="json-array">' : '{</span><ul class="json-object">';
  114. for (var key in json) {
  115. if (json.hasOwnProperty(key)) {
  116. let comma = --len > 0 ? ',' : '',
  117. jsonPath = parentPath + '.' + key,
  118. collapse = isCollapsable(json[key]) ? '<a href class="json-toggle"></a>' : '',
  119. res = json2html(json[key], jsonPath)
  120. let toHtml = type === 'array' ? res : `<span class="json-key">"${key}"</span>: ${res}`
  121. html += [`<li json-path="${jsonPath}">`, collapse, toHtml, comma, '</li>'].join('')
  122. }
  123. }
  124.  
  125. if(type === 'array'){
  126. html += '</ol><span class="json-brackets">]</span>'
  127. }else{
  128. html += '</ul><span class="json-brackets">}</span>'
  129. }
  130. }else{
  131. html += '<span class="json-brackets">'
  132. html += (type === 'array') ? '[]' : '{}'
  133. html += '</span>'
  134. }
  135. break
  136. default:
  137. /* Escape tags */
  138. json = type === 'string' ? json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;') : json
  139. if (Utils.isUrl(json)){
  140. html += `<a target="_blank" href="${json}" class="json-string">"${json}"</a>`;
  141. }else{
  142. json = type === 'string' ? `"${json}"` : json
  143. html += `<span class="json-${type}">${json}</span>`;
  144. }
  145. break
  146. }
  147. return html;
  148. }
  149.  
  150. $.fn.jsonViewer = function(json, jsonpFun) {
  151. return this.each(function() {
  152. /* Transform to HTML */
  153. var html = json2html(json);
  154. /** is JSONP */
  155. if(jsonpFun !== undefined && jsonpFun !== null){
  156. html = `<div>${jsonpFun}(</div>${html}<div>)</div>`
  157. }
  158. /* Insert HTML in target DOM element */
  159. $(this).html(html);
  160.  
  161. /* Bind click on toggle buttons */
  162. $(this).off('click');
  163. $(this).on('click', 'a.json-toggle', function() {
  164. var target = $(this).toggleClass('collapsed').siblings('ul.json-object, ol.json-array');
  165. target.toggle();
  166. if (target.is(':visible')) {
  167. target.siblings('.json-placeholder').remove();
  168. }else {
  169. var count = target.children('li:not([class*="hidden"])').length;
  170. var placeholder = count + (count > 1 ? ' items' : ' item');
  171. target.after('<a href class="json-placeholder">' + placeholder + '</a>');
  172. }
  173. return false;
  174. });
  175.  
  176. /* Simulate click on toggle button when placeholder is clicked */
  177. $(this).on('click', 'a.json-placeholder', function() {
  178. $(this).siblings('a.json-toggle').click();
  179. $(this).siblings('a.json-placeholder').remove();
  180. return false;
  181. });
  182. });
  183. };
  184. })(_jQuery);
  185. // jquery.json-viewer 插件 结束
  186.  
  187. (function($){
  188. var source = $('pre[style="word-wrap: break-word; white-space: pre-wrap;"]').first();
  189. if(source.length === 0){
  190. return
  191. }
  192. let rawText = source.text()
  193. if(!rawText){
  194. return
  195. }
  196.  
  197. // 判断是否为jsonp格式
  198. let jsonpFun = null, tokens = rawText.match(/^([^\s(]*)\s*\(([\s\S]*)\)\s*;?$/)
  199. if (tokens && tokens[1] && tokens[2]) {
  200. jsonpFun = tokens[1]
  201. rawText= tokens[2]
  202. }
  203.  
  204. if(!Utils.isJSON(rawText)){
  205. return
  206. }
  207.  
  208. // 随机rgb颜色
  209. let rgbaColor = `${Math.random()*256}, ${Math.random()*256}, ${Math.random()*256}`
  210. // 添加样式
  211. GM_addStyle(GM_getResourceText('swalStyle'))
  212. // GM_addStyle(GM_getResourceText('layerStyle'))
  213. GM_addStyle(GM_getResourceText('treetableStyle'))
  214. $("head").append(`<link rel="stylesheet" type="text/css" href="https://unpkg.com/layer-src@3.5.1/dist/theme/default/layer.css"/>`)
  215.  
  216. GM_addStyle(`
  217. body, html{
  218. margin: 0;
  219. padding: 0;
  220. font-size: 14px;
  221. }
  222. td{
  223. font-size: 14px;
  224. }
  225. li::marker {
  226. content: '';
  227. }
  228. .hidden{
  229. display: none !important;
  230. }
  231. .scroll-top{
  232. width: 48px;
  233. height: 48px;
  234. z-index: 999;
  235. position: fixed;
  236. right: 30px;
  237. bottom: 30px;
  238. background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAA39JREFUaEPtmV2ITVEUx39rJjT5KPKC5DPKK6UGZZ7Eq0whNUiKFPfcKW9mnmjm7ssLaSLygEx5xItMESnKC4V8FV4IJUkyS+fMHZ07zjl3733PvT6a+3S6Z629/r+99sfa+wgpPw1YDtxLe9/k/1eI4X5STMkSokXWNlloYjgpMZSmIxPgbxBfS8M4QK0eavT78Qw0uodrtd+wDGjAMqBcEVAQw6NaYnzeNwRAi2xBOQlMrYj6jLJTygz6iMzyyR1Ai+xCGUgMqnTmDZErgBYoIJjMXhY2S4mLeWUiNwAt0INwyEqY0iNleq1saxjlAqAB64BrToKEHVLijJNPgnHdALqPaUziOsoKRzGfaKFD+nng6FdlXj+Ay9AZq1Q5L2W2/jEA3c8sWqOSe3aCiK/A3cr/K4G2FKEbxXDZF6KuDKROXKWXFvqlxJdQmBaZzDDdKZP8thhW/RmAgKfA4qrgSq+U6UkSlAostEuJOz4Q3hnQgPXAld+CCh1pB5DogKTcSBAalhpHmw1wGtgxJuhbMczJEqIBbxLmzFUxbGg2gCYE9AVADF6jwctJDzKd73xI7DG/IQQTmCFH+OiaBT+AAyykhWcpBZv7JA4bGmaRHOV5cwBqX7mc5Qe9coyX0TK6n/m0RnVSV4bA1KuTLCi/DBTYhHCpRm+Fe8Bo/b8JmJxp71lqOwNowB7guGuqLe33iuGEpW1k5gTgVDK7qIjbZmyESU1aA1TOuA99dTn5CQulxAsbH3uAeqpOGyXVNrvEcMrGzR4gIGnjsonhYzMkhg4bx3EAm17ysGlIBh4DSzzE+LgMiGG3jaPLEApv2Q7YNFq3jcOmZg9gt/u6aH8PzEx0aGWu9PHapjFrgKimCaIDTHiQqf4pNxHW2AT8ZaMEiZdgSreUKdm25QRQgRi7nIZzI7xZcPmeNkAL5xjmVpVQx1049HUGiCAKFBH6K8GjFUP3MIW2CGJpjd4bFEPnmM54grA761tYWpteAJXgy1A6w6p09Opcu1nED7ZlAcQP/BqwGZjORAblMO9sh03czhsgLZgW6EL4JoYL0b0RTArPBfFnH6G5ZyAVYKTkGBlWQVR2bxDDgvjzvwQQXqGsDQ/sGkTXKdHzOECsB3LtjdjKMjqE/qMMjJwn5olh+98+hM6gvAqXy0aN+4Yuo/HG/weAPmC1GNrzHDbxtn4Coc0pQNdM3UAAAAAASUVORK5CYII=)
  239. }
  240. /** 工具栏样式 START **/
  241. .flex-container{
  242. height: 100vh;
  243. display: flex;
  244. flex-direction: column;
  245. }
  246. .tabs, .toolbar{
  247. display: flex;
  248. line-height: 28px;
  249. background: #f3f3f3;
  250. border-bottom: 1px solid #e0e0e2;
  251. }
  252.  
  253. .toolbar{
  254. line-height: 23px;
  255. box-shadow: 0px 3px 5px 0 #ddd;
  256. }
  257. .searchbox{
  258. display: flex;
  259. flex-grow: 1;
  260. }
  261. .toolbar input{
  262. flex-grow: 1;
  263. border: none;
  264. outline: none;
  265. font-size: 12px;
  266. padding-left: 23px;
  267. background-size: 12px;
  268. background-repeat: no-repeat;
  269. background-position: 7px center;
  270. background-image: url(data:image/svg+xml;base64,PCEtLSBUaGlzIFNvdXJjZSBDb2RlIEZvcm0gaXMgc3ViamVjdCB0byB0aGUgdGVybXMgb2YgdGhlIE1vemlsbGEgUHVibGljCiAgIC0gTGljZW5zZSwgdi4gMi4wLiBJZiBhIGNvcHkgb2YgdGhlIE1QTCB3YXMgbm90IGRpc3RyaWJ1dGVkIHdpdGggdGhpcwogICAtIGZpbGUsIFlvdSBjYW4gb2J0YWluIG9uZSBhdCBodHRwOi8vbW96aWxsYS5vcmcvTVBMLzIuMC8uIC0tPgo8c3ZnIGZpbGw9InJnYmEoMTM1LCAxMzUsIDEzNywgMC45KSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMTIgMTIiPgogIDxwYXRoIGZpbGw9ImNvbnRleHQtZmlsbCIgb3BhY2l0eT0iLjQiIGQ9Ik01IDkuMmwyIDEuNlY2LjFMOC41NSA0aC01LjFMNSA2LjF2My4xeiIvPgogIDxwYXRoIGZpbGw9ImNvbnRleHQtZmlsbCIgZD0iTTEuMTggMi42QTEgMSAwIDAgMSAyIDFIMTBhMSAxIDAgMCAxIC44IDEuNkw4IDYuNHY0LjgyYzAgLjYzLS43Mi45OC0xLjIyLjZsLTIuNS0xLjk5QS43NS43NSAwIDAgMSA0IDkuMjVWNi40MUwxLjE4IDIuNnpNMiAyTDUgNi4wOXYzLjA0bDIgMS41OVY2LjA5TDEwLjAxIDJIMnoiLz4KPC9zdmc+Cg==);
  271. }
  272. .clear {
  273. flex: 0 0 auto;
  274. align-self: center;
  275. margin: 0 4px;
  276. padding: 0;
  277. border: 0;
  278. width: 16px;
  279. height: 16px;
  280. background-color: transparent;
  281. background-image: url(data:image/svg+xml;base64,PCEtLSBUaGlzIFNvdXJjZSBDb2RlIEZvcm0gaXMgc3ViamVjdCB0byB0aGUgdGVybXMgb2YgdGhlIE1vemlsbGEgUHVibGljCiAgIC0gTGljZW5zZSwgdi4gMi4wLiBJZiBhIGNvcHkgb2YgdGhlIE1QTCB3YXMgbm90IGRpc3RyaWJ1dGVkIHdpdGggdGhpcwogICAtIGZpbGUsIFlvdSBjYW4gb2J0YWluIG9uZSBhdCBodHRwOi8vbW96aWxsYS5vcmcvTVBMLzIuMC8uIC0tPgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiB2aWV3Qm94PSIwIDAgMTYgMTYiIGZpbGw9ImNvbnRleHQtZmlsbCIgZmlsbC1vcGFjaXR5PSJjb250ZXh0LWZpbGwtb3BhY2l0eSI+CiAgPHBhdGggZD0iTTYuNTg2IDhsLTIuMjkzIDIuMjkzYTEgMSAwIDAgMCAxLjQxNCAxLjQxNEw4IDkuNDE0bDIuMjkzIDIuMjkzYTEgMSAwIDAgMCAxLjQxNC0xLjQxNEw5LjQxNCA4bDIuMjkzLTIuMjkzYTEgMSAwIDEgMC0xLjQxNC0xLjQxNEw4IDYuNTg2IDUuNzA3IDQuMjkzYTEgMSAwIDAgMC0xLjQxNCAxLjQxNEw2LjU4NiA4ek04IDBhOCA4IDAgMSAxIDAgMTZBOCA4IDAgMCAxIDggMHoiLz4KPC9zdmc+Cg==);
  282. }
  283. .tabs-item, .toolbar-item{
  284. cursor: pointer;
  285. padding: 0 10px;
  286. font-size: 12px;
  287. border-top: 2px solid #f3f3f3;
  288. }
  289. .tabs-item.active{
  290. color: #0060df;
  291. border-top: 2px solid #0060df !important;
  292. background: #e9e9e9;
  293. }
  294. .tabs-item:hover{
  295. background: #e9e9e9;
  296. border-top: 2px solid #c3c3c6;
  297. }
  298. .toolbar-item:hover{
  299. background: #e9e9e9;
  300. border-top: 2px solid #e9e9e9;
  301. }
  302. /** 工具栏样式 END **/
  303. /** JSON 格式化样式 START **/
  304. ul.json-object,
  305. ul.json-array {
  306. list-style-type: none;
  307. margin: 0 0 0 2px;
  308. border-left: 1px dotted #5D6D7E;
  309. padding-left: 24px;
  310. }
  311. .json-brackets {
  312. font-weight: 700;
  313. }
  314. .json-key {
  315. /* color: #A31515;*/
  316. color: #910F93;
  317. cursor: pointer;
  318. }
  319. .json-string, .json-string a{
  320. /* color: #0b7500;*/
  321. color: #4B8A4C;
  322. }
  323. .json-number{
  324. /* color: #164FF0;*/
  325. color: #1a01cc;
  326. font-weight: 600;
  327. }
  328. .json-boolean{
  329. color: #905;
  330. font-weight: 600;
  331. }
  332. .json-null {
  333. /* color: #F1592A;*/
  334. color: #0031BC;
  335. font-weight: 600;
  336. }
  337. a.json-toggle {
  338. position: rElative;
  339. color: inherit;
  340. opacity: 0.2;
  341. text-decoration: none;
  342. }
  343. a.json-toggle:hover {
  344. opacity: 0.35;
  345. }
  346. a.json-toggle:active {
  347. opacity: 0.5;
  348. }
  349. a.json-toggle:focus {
  350. outline: none;
  351. }
  352. a.json-toggle:before {
  353. top: 2.5px;
  354. left: -15px;
  355. position: absolute;
  356. content: "";
  357. display: block;
  358. width: 0;
  359. height: 0;
  360. border-style: solid;
  361. border-width: 5px 0 5px 8px;
  362. border-color: transparent transparent transparent currentColor;
  363. transform: rotate(90deg);
  364. }
  365. a.json-toggle.collapsed:before {
  366. transform: rotate(0deg);
  367. }
  368. a.json-placeholder {
  369. color: #aaa;
  370. font-size: 12px;
  371. padding: 0 1em;
  372. text-decoration: none;
  373. }
  374. a.json-placeholder:hover {
  375. text-decoration: underline;
  376. }
  377. /** JSON 格式化样式 END **/
  378. /** 脑图样式 START **/
  379. #jmContainer{
  380. width: 100vw;
  381. height: calc(100vh - 57px);
  382. /* background:#F7F7F7 */
  383. }
  384. jmnode{
  385. display: flex;
  386. align-items: center;
  387. padding: 0 7px 0 22px;
  388. }
  389. jmnode{
  390. color: #475872 !important;
  391. box-shadow: none !important;
  392. background-color: transparent !important;
  393. }
  394. jmnode:hover{
  395. text-shadow: 1px 1px 1px currentColor;
  396. }
  397. jmnode.root {
  398. padding: 0;
  399. color: transparent !important;
  400. }
  401. jmnode:not(.root)::before, jmnode.root::before{
  402. content: " ";
  403. top: 50%;
  404. position: absolute;
  405. border-radius: 50%;
  406. transform: translateY(-50%);
  407. }
  408. jmnode:not(.root)::before{
  409. left: 0;
  410. width: 15px;
  411. height: 15px;
  412. background: rgba(${rgbaColor}, 0.5);
  413. }
  414. jmnode.root::before{
  415. left: 50%;
  416. width: 18px;
  417. height: 18px;
  418. transform: translate(-18px, -50%);
  419. background: rgba(${rgbaColor}, 0.7);
  420. }
  421. jmexpander{
  422. margin-top: 1px;
  423. line-height: 9px;
  424. }
  425. /** 脑图样式 END **/
  426.  
  427. .layui-layer-tips{
  428. width: auto !important;
  429. }
  430.  
  431. .mind-array{
  432. opacity: 0.5;
  433. font-size: 12px;
  434. padding-left: 5px;
  435. }
  436. /** 容器样式 START **/
  437. .tabs-container{
  438. overflow: auto;
  439. line-height: 1.5;
  440. font-family: monospace;
  441. }
  442. .tabs-container > div{
  443. display: none;
  444. }
  445. .tabs-container > div.active{
  446. display: block;
  447. }
  448. .tabs-container #formater{
  449. padding: 10px;
  450. }
  451. .tabs-container #rawText{
  452. padding: 0 10px;
  453. }
  454. .tabs-container #rawText pre{
  455. display: block !important;
  456. }
  457. /** 容器样式 END **/
  458.  
  459. table.treetable{
  460. border: none;
  461. }
  462. .treetable tbody tr td {
  463. line-height: 16px;
  464. }
  465. table.treetable .indenter a {
  466. width: 20px;
  467. display: inline-block;
  468. text-decoration: none;
  469. }
  470. table.treetable tr:hover{
  471. background: #f0f9fe;
  472. }
  473. table.treetable tr.selected td, table.treetable tr.selected td a{
  474. color: #fff !important;
  475. background-color: #3875d7 !important;
  476. }
  477. table.treetable tbody tr td:first-child{
  478. width: 100px;
  479. }
  480. table.treetable tr.branch{
  481. background: none;
  482. }
  483. table.treetable span.json-brackets{
  484. padding: 0;
  485. }
  486. `)
  487.  
  488. source.hide()
  489. // 将内容用eval函数处理下
  490. const jsonObject = eval('(' + rawText + ')');
  491.  
  492. $("body").append(`
  493. <div class="scroll-top"></div>
  494. <div class="flex-container">
  495. <div class="panel">
  496. <div class="tabs">
  497. <div class="tabs-item btn active" id="formaterJSON">JSON格式化</div>
  498. <div class="tabs-item btn" id="showMind">JSON脑图</div>
  499. <div class="tabs-item btn" id="switchRawText">原始数据</div>
  500. </div>
  501. <div class="toolbar">
  502. <div class="toolbar-item btn" id="saveJson">保存</div>
  503. <div class="toolbar-item btn" id="copyJson">复制</div>
  504. <div class="toolbar-item btn" id="collapseAll">全部折叠</div>
  505. <div class="toolbar-item btn" id="expandAll">全部展开</div>
  506. <div class="toolbar-item btn" id="formaterRawText" style="display: none">美化输出</div>
  507. <div class="searchbox">
  508. <input type="text" placeholder="过滤 JSON "/>
  509. <button class="clear" hidden></button>
  510. </div>
  511. </div>
  512. </div>
  513. <div class="tabs-container">
  514. <div class="active" id="formater"></div>
  515. <div id="jmContainer"></div>
  516. <div id="rawText"><pre></pre></div>
  517. </div>
  518. </div>`)
  519.  
  520. let btnEvent = {
  521. isFormater: false,
  522. $rawText: $('#rawText'),
  523. /**
  524. * 保存为文件
  525. */
  526. download: {
  527. download: function(content, filename) {
  528. const link = document.createElement("a")
  529. link.href = content
  530. link.download = filename
  531. link.click()
  532. },
  533. saveJSON: function (text) {
  534. // 创建一个 Blob 对象,包含要下载的文本内容
  535. const blob = new Blob([text], { type: "text/plain;charset=utf-8" });
  536. const url = URL.createObjectURL(blob)
  537. let filename = new Date().getTime() + '.json';
  538. this.download(url, filename)
  539. URL.revokeObjectURL(url);
  540. },
  541. savePNG: () => jm.shoot(),
  542. },
  543. saveJson:function(){
  544. if($('#jmContainer').is(':visible')){
  545. this.download.savePNG()
  546. }else{
  547. this.download.saveJSON(this.$rawText.text())
  548. }
  549. },
  550. // 复制JSON文本内容
  551. copyJson: function(){
  552. GM_setClipboard(this.$rawText.text())
  553. layer.msg('复制成功', {time: 1500})
  554. },
  555. // 全部折叠
  556. collapseAll: function(){
  557. if($('#formater').is(':visible')){
  558. try{
  559. $('.json-toggle').not('.collapsed').click()
  560. }catch(e){}
  561. }else{
  562. jm.collapse_all()
  563. }
  564. },
  565. // 全部展开
  566. expandAll: function(){
  567. if($('#formater').is(':visible')){
  568. try{
  569. $('a.json-placeholder').click().remove()
  570. }catch(e){}
  571. }else{
  572. jm.expand_all()
  573. jm.scroll_node_to_center(jm.get_root())
  574. }
  575. },
  576. formaterJSON: function(){},
  577. // 显示JSON脑图
  578. showMind: function(){},
  579. // 查看原始JSON内容
  580. switchRawText: function(){
  581. this.$rawText.html(source.clone())
  582. },
  583. // 美化
  584. formaterRawText: function(){
  585. this.isFormater = !this.isFormater
  586. if(this.isFormater){
  587. this.$rawText.find('pre').text(JSON.stringify(jsonObject, null, 2))
  588. }else{
  589. this.switchRawText()
  590. }
  591. },
  592. init: function(){
  593. this.switchRawText()
  594.  
  595. // 按钮点击事件
  596. $('.btn').click(e => {
  597. const target = e.target, id = target.id
  598. if(target.classList.contains('tabs-item')){
  599. let index = $(target).index()
  600. $(target).addClass('active').siblings().removeClass("active")
  601. $('.tabs-container > div').removeClass("active").eq(index).addClass('active')
  602.  
  603. let rEl = $('#formaterRawText'),
  604. fEl= $('.searchbox'),
  605. cEl= $('#copyJson'),
  606. aEl = $('#collapseAll, #expandAll')
  607. id === 'formaterJSON' ? fEl.show(): fEl.hide()
  608. id === 'showMind' ? cEl.hide(): cEl.show()
  609. id === 'switchRawText' ? (rEl.show() && aEl.hide()) : (rEl.hide() && aEl.show())
  610. }
  611. this[id](target)
  612. })
  613.  
  614. return this
  615. }
  616. },
  617. jsonMind = {
  618. // JSON数据转换为jsMind所需要的数据结构
  619. convert: function(json){
  620. let children = []
  621. if(typeof json === 'object'){
  622. for(let key in json){
  623. let val = json[key],
  624. isArray = Array.isArray(val)
  625.  
  626. children.push({
  627. isArray,
  628. chain: key,
  629. id: key + '_' + Math.random(),
  630. topic: isArray ? `${key}<span class="mind-array">[${val.length}]</span>` : `${key}`,
  631. // children: this.convert(val)
  632. children: isArray ? this.convert(val[0]) : this.convert(val)
  633. })
  634. }
  635. }
  636. return children;
  637. },
  638. // 脑图节点调用链
  639. mindChain: function (node){
  640. let chain = node.data.chain
  641. if(!node.parent){
  642. return chain
  643. }
  644.  
  645. let parent = node.parent, parentChain = this.mindChain(parent)
  646. chain = parent.data.isArray ? `${parentChain}[0].${chain}` : `${parentChain}.${chain}`
  647. return chain
  648. },
  649. // 显示脑图
  650. show: function(json, isArr){
  651. jm.show({
  652. "meta":{
  653. "name":"JSON脑图",
  654. "author":"1220301855@qq.com",
  655. "version":"1.0"
  656. },
  657. "format":"node_tree",
  658. /* 数据内容 */
  659. "data": {
  660. "id": "root",
  661. "topic": 'Response',
  662. "direction": "left",
  663. "children": this.convert(json),
  664. "chain": isArr ? 'Response[0]' : 'Response'
  665. }
  666. })
  667.  
  668. setTimeout(() => jm.scroll_node_to_center(jm.get_root()), 300)
  669. return this
  670. },
  671. // 脑图节点事件
  672. event:function(){
  673. $("jmnode").on('dblclick mouseover mouseout', function(event){
  674. let that = $(this), node = jm.get_node(that.attr('nodeid'))
  675. if(!node.parent){
  676. return
  677. }
  678.  
  679. switch(event.type){
  680. case 'dblclick':
  681. GM_setClipboard(jsonMind.mindChain(node))
  682. layer.msg('节点路径复制成功', {time: 1500})
  683. break;
  684. case 'mouseover':
  685. let s = `<b>节点路径(双击复制)</b><br/>${jsonMind.mindChain(node)}`
  686. layer.tips(s, that, {
  687. time: 0,
  688. tips: [2, '#1e2732']
  689. });
  690. break;
  691. default:
  692. layer.closeAll()
  693. break;
  694. }
  695. })
  696. return this
  697. },
  698. init: function(json){
  699. let isArr = Array.isArray(json);
  700. if(isArr){
  701. if(typeof json[0] !== 'object'){
  702. layer.msg('数据结构无法生成脑图', {time: 1000})
  703. return
  704. }
  705. json = json[0]
  706. }
  707.  
  708. if(!window.jm){
  709. window.jm = new jsMind({
  710. mode :'side',
  711. editable: false,
  712. container:'jmContainer',
  713. view: {
  714. hmargin: 50, // 思维导图距容器外框的最小水平距离
  715. vmargin: 50, // 思维导图距容器外框的最小垂直距离
  716. engine: 'svg', // 思维导图各节点之间线条的绘制引擎
  717. draggable: true, // 当容器不能完全容纳思维导图时,是否允许拖动画布代替鼠标滚动
  718. support_html : false,
  719. line_color: '#C4C9D0',
  720. },
  721. layout: {
  722. vspace: 7, // 节点之间的垂直间距
  723. hspace: 150, // 节点之间的水平空间
  724. },
  725. });
  726. }
  727.  
  728. this.show(json, isArr).event()
  729. }
  730. },
  731. otherOperate = {
  732. // 过滤 JSON
  733. filterJSON: function(filter) {
  734. if(!filter){
  735. $('li').removeClass('hidden')
  736. return
  737. }
  738.  
  739. let chainSet= new Set()
  740. /**
  741. * JSON key
  742. * 假如 filter === i, querySelectorAll得到DOM节点
  743. * 得到:['/feedList/0/images/0/user_id', '/feedList/0/images/0', '/feedList/0/images', '/feedList/0', '/feedList']
  744. */
  745. document.querySelectorAll('#formater *[json-path]').forEach(el => {
  746. let chain = $(el).attr('json-path')
  747. if(!chain){
  748. return
  749. }
  750. let newChain = chain.substr(chain.lastIndexOf('.'))
  751. if(!newChain.toLowerCase().includes(filter.toLowerCase())){
  752. return
  753. }
  754. chainSet.add(chain)
  755. while(chain = chain.substr(0, chain.lastIndexOf('.'))){
  756. chainSet.add(chain)
  757. }
  758. })
  759.  
  760. /**
  761. * JSON value
  762. */
  763. document.querySelectorAll("#formater *[class*='json-']:not([class*='json-key']):not([class*='json-brackets'])")
  764. .forEach(el =>{
  765. let target = $(el),
  766. chain = target.parent().attr('json-path')
  767. if(!chain){
  768. return
  769. }
  770. let text = target.text()
  771. if(!text.toLowerCase().includes(filter.toLowerCase())){
  772. return
  773. }
  774. chainSet.add(chain)
  775. while(chain = chain.substr(0, chain.lastIndexOf('.'))){
  776. chainSet.add(chain)
  777. }
  778. })
  779.  
  780. $('li').addClass('hidden')
  781. chainSet.forEach(chain => {
  782. $(`*[json-path="${chain}"]`).removeClass('hidden')
  783. })
  784. },
  785. // JSON 过滤
  786. input: function(){
  787. let that = this
  788. $('input').on('input', function(){
  789. let val = $(this).val()
  790. val === '' ? $('.clear').attr('hidden', true) : $('.clear').attr('hidden', false)
  791. that.filterJSON(val)
  792. })
  793. return that
  794. },
  795. // 清空输入框内容
  796. clear: function(){
  797. let that = this
  798. $('.clear').click(function(){
  799. that.filterJSON()
  800. $('input').val('')
  801. $(this).attr('hidden', true)
  802. })
  803. return this
  804. },
  805. // 返回顶部
  806. scrollTop: function(){
  807. $('.scroll-top').click(function(){
  808. $('.tabs-container').animate({
  809. scrollTop: '0'
  810. }, 1000);
  811. })
  812. return this
  813. },
  814. urlHover:function(){
  815. // 所有a标签,看是否是图片,是图片生成预览图
  816. $("a.json-string[href]").hover(function(){
  817. var that = $(this),
  818. href = that.attr('href')
  819. if(Utils.isImg(href)){
  820. layer.tips(`<img src="${href}" />`, that, {
  821. time: 0,
  822. anim: 5,
  823. maxWidth: 500,
  824. tips: [3, '#d9d9d9']
  825. });
  826. }
  827. }, () => layer.closeAll())
  828. return this
  829. },
  830. jsonKeyHover: function(){
  831. $(".json-key").hover(function(){
  832. var that = $(this),
  833. jsonPath = that.parent().attr('json-path')
  834. if(jsonPath){
  835. layer.tips(jsonPath, that, {
  836. time: 0,
  837. anim: 5,
  838. maxWidth: 500,
  839. tips: [1, '#1e2732']
  840. });
  841. }
  842. }, () => layer.closeAll())
  843. return this
  844. },
  845. init:function(){
  846. this.input().clear().scrollTop().urlHover().jsonKeyHover()
  847. }
  848. }
  849.  
  850. $('#formater').jsonViewer(jsonObject, jsonpFun)
  851. btnEvent.init()
  852. otherOperate.init()
  853. jsonMind.init(jsonObject)
  854.  
  855. })(_jQuery)
  856. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址