format-lib.js 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865
  1. /**
  2. * 日期格式化
  3. * @param {Object} pattern
  4. */
  5. Date.prototype.format = function (pattern) {
  6. let pad = function (source, length) {
  7. let pre = "",
  8. negative = (source < 0),
  9. string = String(Math.abs(source));
  10. if (string.length < length) {
  11. pre = (new Array(length - string.length + 1)).join('0');
  12. }
  13. return (negative ? "-" : "") + pre + string;
  14. };
  15. if ('string' !== typeof pattern) {
  16. return this.toString();
  17. }
  18. let replacer = function (patternPart, result) {
  19. pattern = pattern.replace(patternPart, result);
  20. };
  21. let year = this.getFullYear(),
  22. month = this.getMonth() + 1,
  23. date2 = this.getDate(),
  24. hours = this.getHours(),
  25. minutes = this.getMinutes(),
  26. seconds = this.getSeconds(),
  27. milliSec = this.getMilliseconds();
  28. replacer(/yyyy/g, pad(year, 4));
  29. replacer(/yy/g, pad(parseInt(year.toString().slice(2), 10), 2));
  30. replacer(/MM/g, pad(month, 2));
  31. replacer(/M/g, month);
  32. replacer(/dd/g, pad(date2, 2));
  33. replacer(/d/g, date2);
  34. replacer(/HH/g, pad(hours, 2));
  35. replacer(/H/g, hours);
  36. replacer(/hh/g, pad(hours % 12, 2));
  37. replacer(/h/g, hours % 12);
  38. replacer(/mm/g, pad(minutes, 2));
  39. replacer(/m/g, minutes);
  40. replacer(/ss/g, pad(seconds, 2));
  41. replacer(/s/g, seconds);
  42. replacer(/SSS/g, pad(milliSec, 3));
  43. replacer(/S/g, milliSec);
  44. return pattern;
  45. };
  46. /**
  47. * 自动消失的Alert弹窗
  48. * @param content
  49. */
  50. window.toast = function (content) {
  51. window.clearTimeout(window.feHelperAlertMsgTid);
  52. let elAlertMsg = document.querySelector("#fehelper_alertmsg");
  53. if (!elAlertMsg) {
  54. let elWrapper = document.createElement('div');
  55. elWrapper.innerHTML = '<div id="fehelper_alertmsg" style="position:fixed;bottom:25px;left:5px;z-index:1000000">' +
  56. '<p style="background:#000;display:inline-block;color:#fff;text-align:center;' +
  57. 'padding:10px 10px;margin:0 auto;font-size:14px;border-radius:4px;">' + content + '</p></div>';
  58. elAlertMsg = elWrapper.childNodes[0];
  59. document.body.appendChild(elAlertMsg);
  60. } else {
  61. elAlertMsg.querySelector('p').innerHTML = content;
  62. elAlertMsg.style.display = 'block';
  63. }
  64. window.feHelperAlertMsgTid = window.setTimeout(function () {
  65. elAlertMsg.style.display = 'none';
  66. }, 1000);
  67. };
  68. /**
  69. * FeHelper Json Format Lib,入口文件
  70. * @example
  71. * Formatter.format(jsonString)
  72. */
  73. window.Formatter = (function () {
  74. "use strict";
  75. let jfContent,
  76. jfPre,
  77. jfStyleEl,
  78. jfStatusBar,
  79. formattingMsg;
  80. let lastItemIdGiven = 0;
  81. let cachedJsonString = '';
  82. // 单例Worker实例
  83. let workerInstance = null;
  84. let _initElements = function () {
  85. jfContent = $('#jfContent');
  86. if (!jfContent[0]) {
  87. jfContent = $('<div id="jfContent" />').appendTo('body');
  88. }
  89. jfPre = $('#jfContent_pre');
  90. if (!jfPre[0]) {
  91. jfPre = $('<pre id="jfContent_pre" />').appendTo('body');
  92. }
  93. jfStyleEl = $('#jfStyleEl');
  94. if (!jfStyleEl[0]) {
  95. jfStyleEl = $('<style id="jfStyleEl" />').appendTo('head');
  96. }
  97. formattingMsg = $('#formattingMsg');
  98. if (!formattingMsg[0]) {
  99. formattingMsg = $('<div id="formattingMsg"><span class="x-loading"></span>格式化中...</div>').appendTo('body');
  100. }
  101. try {
  102. jfContent.html('').show();
  103. jfPre.html('').hide();
  104. jfStatusBar && jfStatusBar.hide();
  105. formattingMsg.hide();
  106. } catch (e) {
  107. }
  108. };
  109. /**
  110. * HTML特殊字符格式化
  111. * @param str
  112. * @returns {*}
  113. */
  114. let htmlspecialchars = function (str) {
  115. str = str.replace(/&/g, '&amp;');
  116. str = str.replace(/</g, '&lt;');
  117. str = str.replace(/>/g, '&gt;');
  118. str = str.replace(/"/g, '&quot;');
  119. str = str.replace(/'/g, '&#039;');
  120. return str;
  121. };
  122. /**
  123. * 直接下载,能解决中文乱码
  124. * @param content
  125. * @private
  126. */
  127. let _downloadSupport = function (content) {
  128. // 下载链接
  129. let dt = (new Date()).format('yyyyMMddHHmmss');
  130. let blob = new Blob([content], {type: 'application/octet-stream'});
  131. let button = $('<button class="xjf-btn xjf-btn-right">下载JSON</button>').appendTo('#optionBar');
  132. if (typeof chrome === 'undefined' || !chrome.permissions) {
  133. button.click(function (e) {
  134. let aLink = $('#aLinkDownload');
  135. if (!aLink[0]) {
  136. aLink = $('<a id="aLinkDownload" target="_blank" title="保存到本地">下载JSON数据</a>').appendTo('body');
  137. aLink.attr('download', 'FeHelper-' + dt + '.json');
  138. aLink.attr('href', URL.createObjectURL(blob));
  139. }
  140. aLink[0].click();
  141. });
  142. } else {
  143. button.click(function (e) {
  144. // 请求权限
  145. chrome.permissions.request({
  146. permissions: ['downloads']
  147. }, (granted) => {
  148. if (granted) {
  149. chrome.downloads.download({
  150. url: URL.createObjectURL(blob),
  151. saveAs: true,
  152. conflictAction: 'overwrite',
  153. filename: 'FeHelper-' + dt + '.json'
  154. });
  155. } else {
  156. toast('必须接受授权,才能正常下载!');
  157. }
  158. });
  159. });
  160. }
  161. };
  162. /**
  163. * chrome 下复制到剪贴板
  164. * @param text
  165. */
  166. let _copyToClipboard = function (text) {
  167. let input = document.createElement('textarea');
  168. input.style.position = 'fixed';
  169. input.style.opacity = 0;
  170. input.value = text;
  171. document.body.appendChild(input);
  172. input.select();
  173. document.execCommand('Copy');
  174. document.body.removeChild(input);
  175. toast('Json片段复制成功,随处粘贴可用!')
  176. };
  177. /**
  178. * 从el中获取json文本
  179. * @param el
  180. * @returns {string}
  181. */
  182. let getJsonText = function (el) {
  183. let txt = el.text().replace(/复制\|下载\|删除/gm,'').replace(/":\s/gm, '":').replace(/,$/, '').trim();
  184. if (!(/^{/.test(txt) && /\}$/.test(txt)) && !(/^\[/.test(txt) && /\]$/.test(txt))) {
  185. txt = '{' + txt + '}';
  186. }
  187. try {
  188. txt = JSON.stringify(JSON.parse(txt), null, 4);
  189. } catch (err) {
  190. }
  191. return txt;
  192. };
  193. // 添加json路径
  194. let _showJsonPath = function (curEl) {
  195. let keys = [];
  196. do {
  197. if (curEl.hasClass('item-block')) {
  198. if (!curEl.hasClass('rootItem')) {
  199. keys.unshift('[' + curEl.prevAll('.item').length + ']');
  200. } else {
  201. break;
  202. }
  203. } else {
  204. keys.unshift(curEl.find('>.key').text());
  205. }
  206. if (curEl.parent().hasClass('rootItem') || curEl.parent().parent().hasClass('rootItem')) {
  207. break;
  208. }
  209. curEl = curEl.parent().parent();
  210. } while (curEl.length && !curEl.hasClass('rootItem'));
  211. let path = keys.join('#@#').replace(/#@#\[/g, '[').replace(/#@#/g, '.');
  212. let jfPath = $('#jsonPath');
  213. if (!jfPath.length) {
  214. jfPath = $('<span id="jsonPath"/>').prependTo(jfStatusBar);
  215. }
  216. jfPath.html('当前节点:JSON.' + path);
  217. };
  218. // 给某个节点增加操作项
  219. let _addOptForItem = function (el, show) {
  220. // 下载json片段
  221. let fnDownload = function (event) {
  222. event.stopPropagation();
  223. let txt = getJsonText(el);
  224. // 下载片段
  225. let dt = (new Date()).format('yyyyMMddHHmmss');
  226. let blob = new Blob([txt], {type: 'application/octet-stream'});
  227. if (typeof chrome === 'undefined' || !chrome.permissions) {
  228. // 下载JSON的简单形式
  229. $(this).attr('download', 'FeHelper-' + dt + '.json').attr('href', URL.createObjectURL(blob));
  230. } else {
  231. // 请求权限
  232. chrome.permissions.request({
  233. permissions: ['downloads']
  234. }, (granted) => {
  235. if (granted) {
  236. chrome.downloads.download({
  237. url: URL.createObjectURL(blob),
  238. saveAs: true,
  239. conflictAction: 'overwrite',
  240. filename: 'FeHelper-' + dt + '.json'
  241. });
  242. } else {
  243. toast('必须接受授权,才能正常下载!');
  244. }
  245. });
  246. }
  247. };
  248. // 复制json片段
  249. let fnCopy = function (event) {
  250. event.stopPropagation();
  251. _copyToClipboard(getJsonText(el));
  252. };
  253. // 删除json片段
  254. let fnDel = function (event) {
  255. event.stopPropagation();
  256. if (el.parent().is('#formattedJson')) {
  257. toast('如果连最外层的Json也删掉的话,就没啥意义了哦!');
  258. return false;
  259. }
  260. toast('节点已删除成功!');
  261. el.remove();
  262. jfStatusBar && jfStatusBar.hide();
  263. };
  264. $('.boxOpt').hide();
  265. if (show) {
  266. let jfOptEl = el.children('.boxOpt');
  267. if (!jfOptEl.length) {
  268. jfOptEl = $('<b class="boxOpt">' +
  269. '<a class="opt-copy" title="复制当前选中节点的JSON数据">复制</a>|' +
  270. '<a class="opt-download" target="_blank" title="下载当前选中节点的JSON数据">下载</a>|' +
  271. '<a class="opt-del" title="删除当前选中节点的JSON数据">删除</a></b>').appendTo(el);
  272. } else {
  273. jfOptEl.show();
  274. }
  275. jfOptEl.find('a.opt-download').unbind('click').bind('click', fnDownload);
  276. jfOptEl.find('a.opt-copy').unbind('click').bind('click', fnCopy);
  277. jfOptEl.find('a.opt-del').unbind('click').bind('click', fnDel);
  278. }
  279. };
  280. // 显示当前节点的Key
  281. let _toogleStatusBar = function (curEl, show) {
  282. if (!jfStatusBar) {
  283. jfStatusBar = $('<div id="statusBar"/>').appendTo('body');
  284. }
  285. if (!show) {
  286. jfStatusBar.hide();
  287. return;
  288. } else {
  289. jfStatusBar.show();
  290. }
  291. _showJsonPath(curEl);
  292. };
  293. /**
  294. * 折叠所有
  295. * @param elements
  296. */
  297. function collapse(elements) {
  298. let el;
  299. $.each(elements, function (i) {
  300. el = $(this);
  301. if (el.children('.kv-list').length) {
  302. el.addClass('collapsed');
  303. if (!el.attr('id')) {
  304. el.attr('id', 'item' + (++lastItemIdGiven));
  305. let count = el.children('.kv-list').eq(0).children().length;
  306. // Generate comment text eg "4 items"
  307. let comment = count + (count === 1 ? ' item' : ' items');
  308. // Add CSS that targets it
  309. jfStyleEl[0].insertAdjacentHTML(
  310. 'beforeend',
  311. '\n#item' + lastItemIdGiven + '.collapsed:after{color: #aaa; content:" // ' + comment + '"}'
  312. );
  313. }
  314. }
  315. });
  316. }
  317. /**
  318. * 创建几个全局操作的按钮,置于页面右上角即可
  319. * @private
  320. */
  321. let _buildOptionBar = function () {
  322. let optionBar = $('#optionBar');
  323. if (optionBar.length) {
  324. optionBar.html('');
  325. } else {
  326. optionBar = $('<span id="optionBar" />').appendTo(jfContent.parent());
  327. }
  328. $('<span class="x-split">|</span>').appendTo(optionBar);
  329. let buttonFormatted = $('<button class="xjf-btn xjf-btn-left">元数据</button>').appendTo(optionBar);
  330. let buttonCollapseAll = $('<button class="xjf-btn xjf-btn-mid">折叠所有</button>').appendTo(optionBar);
  331. let plainOn = false;
  332. buttonFormatted.bind('click', function (e) {
  333. if (plainOn) {
  334. plainOn = false;
  335. jfPre.hide();
  336. jfContent.show();
  337. buttonFormatted.text('元数据');
  338. } else {
  339. plainOn = true;
  340. jfPre.show();
  341. jfContent.hide();
  342. buttonFormatted.text('格式化');
  343. }
  344. jfStatusBar && jfStatusBar.hide();
  345. });
  346. buttonCollapseAll.bind('click', function (e) {
  347. // 如果内容还没有格式化过,需要再格式化一下
  348. if (plainOn) {
  349. buttonFormatted.trigger('click');
  350. }
  351. if (buttonCollapseAll.text() === '折叠所有') {
  352. buttonCollapseAll.text('展开所有');
  353. collapse($('.item-object,.item-block'));
  354. } else {
  355. buttonCollapseAll.text('折叠所有');
  356. $('.item-object,.item-block').removeClass('collapsed');
  357. }
  358. jfStatusBar && jfStatusBar.hide();
  359. });
  360. };
  361. // 附加操作
  362. let _addEvents = function () {
  363. // 折叠、展开
  364. $('#jfContent span.expand').bind('click', function (ev) {
  365. ev.preventDefault();
  366. ev.stopPropagation();
  367. let parentEl = $(this).parent();
  368. parentEl.toggleClass('collapsed');
  369. if (parentEl.hasClass('collapsed')) {
  370. collapse(parentEl);
  371. }
  372. });
  373. // 点击选中:高亮
  374. $('#jfContent .item').bind('click', function (e) {
  375. let el = $(this);
  376. if (el.hasClass('x-selected')) {
  377. _toogleStatusBar(el, false);
  378. _addOptForItem(el, false);
  379. el.removeClass('x-selected');
  380. e.stopPropagation();
  381. return true;
  382. }
  383. $('.x-selected').removeClass('x-selected');
  384. el.addClass('x-selected');
  385. // 显示底部状态栏
  386. _toogleStatusBar(el, true);
  387. _addOptForItem(el, true);
  388. if (!$(e.target).is('.item .expand')) {
  389. e.stopPropagation();
  390. } else {
  391. $(e.target).parent().trigger('click');
  392. }
  393. // 触发钩子
  394. if (typeof window._OnJsonItemClickByFH === 'function') {
  395. window._OnJsonItemClickByFH(getJsonText(el));
  396. }
  397. });
  398. };
  399. /**
  400. * 初始化或获取Worker实例
  401. * 使用单例模式确保只创建一个Worker
  402. */
  403. let _getWorkerInstance = function() {
  404. if (workerInstance) {
  405. return workerInstance;
  406. }
  407. try {
  408. // 创建内联Worker
  409. // 这个版本包含基本的JSON格式化功能
  410. let workerCode = `
  411. // 创建一个处理BigInt的JSON解析器
  412. const JSONBigInt = {
  413. // 自定义的parse方法,处理大数字
  414. parse: function(text) {
  415. // 先尝试预处理字符串,将可能的大整数标记出来
  416. // 以更精确的方式匹配JSON中的大整数
  417. const preparedText = this._markBigInts(text);
  418. try {
  419. // 使用标准JSON解析,同时使用reviver函数还原BigInt
  420. return JSON.parse(preparedText, this._reviver);
  421. } catch (e) {
  422. // 如果处理失败,尝试原始解析方式
  423. console.error('BigInt处理失败,回退到标准解析', e);
  424. return JSON.parse(text);
  425. }
  426. },
  427. // 将JSON字符串中的大整数标记为特殊格式
  428. _markBigInts: function(text) {
  429. // 这个正则匹配JSON中的数字,但需要避免匹配到引号内的字符串
  430. // 匹配模式: 找到数字前面是冒号或左方括号的情况(表示这是个值而不是键名)
  431. return text.replace(
  432. /([:,\\[]\\s*)(-?\\d{16,})([,\\]\\}])/g,
  433. function(match, prefix, number, suffix) {
  434. // 将大数字转换为特殊格式的字符串
  435. return prefix + '"__BigInt__' + number + '"' + suffix;
  436. }
  437. );
  438. },
  439. // 恢复函数,将标记的BigInt字符串转回BigInt类型
  440. _reviver: function(key, value) {
  441. // 检查是否是我们标记的BigInt字符串
  442. if (typeof value === 'string' && value.startsWith('__BigInt__')) {
  443. // 提取数字部分
  444. const numStr = value.substring(10);
  445. try {
  446. // 尝试转换为BigInt
  447. return BigInt(numStr);
  448. } catch (e) {
  449. // 如果转换失败,保留原始字符串
  450. console.warn('无法转换为BigInt:', numStr);
  451. return numStr;
  452. }
  453. }
  454. return value;
  455. }
  456. };
  457. // 处理主线程消息
  458. self.onmessage = function(event) {
  459. // 格式化JSON
  460. if (event.data.jsonString) {
  461. // 发送格式化中的消息
  462. self.postMessage(['FORMATTING']);
  463. try {
  464. // 先预处理JSON字符串,防止大整数丢失精度
  465. let jsonObj;
  466. try {
  467. // 尝试使用自定义的BigInt解析器
  468. jsonObj = JSONBigInt.parse(event.data.jsonString);
  469. } catch (e) {
  470. // 如果解析失败,回退到标准解析
  471. console.error('BigInt解析失败,回退到标准解析', e);
  472. jsonObj = JSON.parse(event.data.jsonString);
  473. }
  474. // 如果是简单主题,直接返回格式化的JSON
  475. if (event.data.skin && event.data.skin === 'theme-simple') {
  476. // 处理BigInt特殊情况
  477. let formatted = JSON.stringify(jsonObj, function(key, value) {
  478. if (typeof value === 'bigint') {
  479. // 移除n后缀,只显示数字本身
  480. return value.toString();
  481. }
  482. // 处理普通数字,避免科学计数法
  483. if (typeof value === 'number' && value.toString().includes('e')) {
  484. // 大数字转为字符串以避免科学计数法
  485. return value.toLocaleString('fullwide', {useGrouping: false});
  486. }
  487. return value;
  488. }, 4);
  489. let html = '<div id="formattedJson"><pre class="rootItem">' +
  490. formatted.replace(/&/g, '&amp;')
  491. .replace(/</g, '&lt;')
  492. .replace(/>/g, '&gt;')
  493. .replace(/"/g, '&quot;')
  494. .replace(/'/g, '&#039;') +
  495. '</pre></div>';
  496. self.postMessage(['FORMATTED', html]);
  497. return;
  498. }
  499. // 默认主题 - 创建更丰富的HTML结构
  500. let html = '<div id="formattedJson">' +
  501. formatJsonToHtml(jsonObj) +
  502. '</div>';
  503. self.postMessage(['FORMATTED', html]);
  504. } catch (e) {
  505. // 处理错误情况
  506. self.postMessage(['FORMATTED', '<div id="formattedJson"><div class="error">格式化失败: ' + e.message + '</div></div>']);
  507. }
  508. }
  509. };
  510. // HTML特殊字符格式化
  511. function htmlspecialchars(str) {
  512. str = str.replace(/&/g, '&amp;');
  513. str = str.replace(/</g, '&lt;');
  514. str = str.replace(/>/g, '&gt;');
  515. str = str.replace(/"/g, '&quot;');
  516. str = str.replace(/'/g, '&#039;');
  517. return str;
  518. }
  519. // 格式化JSON为HTML
  520. function formatJsonToHtml(json) {
  521. return createNode(json).getHTML();
  522. }
  523. // 创建节点
  524. function createNode(value) {
  525. let node = {
  526. type: getType(value),
  527. value: value,
  528. children: [],
  529. getHTML: function() {
  530. switch(this.type) {
  531. case 'string':
  532. return '<div class="item item-line"><span class="string">"' +
  533. htmlspecialchars(this.value) +
  534. '"</span></div>';
  535. case 'number':
  536. // 确保大数字不使用科学计数法
  537. let numStr = typeof this.value === 'number' && this.value.toString().includes('e')
  538. ? this.value.toLocaleString('fullwide', {useGrouping: false})
  539. : this.value;
  540. return '<div class="item item-line"><span class="number">' +
  541. numStr +
  542. '</span></div>';
  543. case 'bigint':
  544. // 对BigInt类型特殊处理,只显示数字,不添加n后缀
  545. return '<div class="item item-line"><span class="number">' +
  546. this.value.toString() +
  547. '</span></div>';
  548. case 'boolean':
  549. return '<div class="item item-line"><span class="bool">' +
  550. this.value +
  551. '</span></div>';
  552. case 'null':
  553. return '<div class="item item-line"><span class="null">null</span></div>';
  554. case 'object':
  555. return this.getObjectHTML();
  556. case 'array':
  557. return this.getArrayHTML();
  558. default:
  559. return '';
  560. }
  561. },
  562. getObjectHTML: function() {
  563. if (!this.value || Object.keys(this.value).length === 0) {
  564. return '<div class="item item-object"><span class="brace">{</span><span class="brace">}</span></div>';
  565. }
  566. let html = '<div class="item item-object">' +
  567. '<span class="expand"></span>' +
  568. '<span class="brace">{</span>' +
  569. '<span class="ellipsis"></span>' +
  570. '<div class="kv-list">';
  571. let keys = Object.keys(this.value);
  572. keys.forEach((key, index) => {
  573. let prop = this.value[key];
  574. let childNode = createNode(prop);
  575. html += '<div class="item">' +
  576. '<span class="quote">"</span>' +
  577. '<span class="key">' + htmlspecialchars(key) + '</span>' +
  578. '<span class="quote">"</span>' +
  579. '<span class="colon">: </span>';
  580. // 添加值
  581. if (childNode.type === 'object' || childNode.type === 'array') {
  582. html += childNode.getHTML();
  583. } else {
  584. html += childNode.getHTML().replace(/^<div class="item item-line">/, '').replace(/<\\/div>$/, '');
  585. }
  586. // 如果不是最后一个属性,添加逗号
  587. if (index < keys.length - 1) {
  588. html += '<span class="comma">,</span>';
  589. }
  590. html += '</div>';
  591. });
  592. html += '</div><span class="brace">}</span></div>';
  593. return html;
  594. },
  595. getArrayHTML: function() {
  596. if (!this.value || this.value.length === 0) {
  597. return '<div class="item item-array"><span class="brace">[</span><span class="brace">]</span></div>';
  598. }
  599. let html = '<div class="item item-array">' +
  600. '<span class="expand"></span>' +
  601. '<span class="brace">[</span>' +
  602. '<span class="ellipsis"></span>' +
  603. '<div class="kv-list">';
  604. this.value.forEach((item, index) => {
  605. let childNode = createNode(item);
  606. html += '<div class="item item-block">';
  607. // 添加值
  608. if (childNode.type === 'object' || childNode.type === 'array') {
  609. html += childNode.getHTML();
  610. } else {
  611. html += childNode.getHTML().replace(/^<div class="item item-line">/, '').replace(/<\\/div>$/, '');
  612. }
  613. // 如果不是最后一个元素,添加逗号
  614. if (index < this.value.length - 1) {
  615. html += '<span class="comma">,</span>';
  616. }
  617. html += '</div>';
  618. });
  619. html += '</div><span class="brace">]</span></div>';
  620. return html;
  621. }
  622. };
  623. return node;
  624. }
  625. // 获取值类型
  626. function getType(value) {
  627. if (value === null) return 'null';
  628. if (value === undefined) return 'undefined';
  629. let type = typeof value;
  630. // 特别处理BigInt类型
  631. if (type === 'bigint') return 'bigint';
  632. if (type === 'object') {
  633. if (Array.isArray(value)) return 'array';
  634. }
  635. return type;
  636. }
  637. `;
  638. // 创建Blob URL并实例化Worker
  639. let blob = new Blob([workerCode], {type: 'application/javascript'});
  640. let workerUrl = URL.createObjectURL(blob);
  641. workerInstance = new Worker(workerUrl);
  642. // 添加错误处理
  643. workerInstance.onerror = function(e) {
  644. // 如果Worker出错,清空实例允许下次重试
  645. workerInstance = null;
  646. // 避免URL内存泄漏
  647. URL.revokeObjectURL(workerUrl);
  648. };
  649. return workerInstance;
  650. } catch (e) {
  651. // 出现任何错误,返回null
  652. workerInstance = null;
  653. return null;
  654. }
  655. };
  656. /**
  657. * 执行代码格式化
  658. */
  659. let format = function (jsonStr, skin) {
  660. cachedJsonString = JSON.stringify(JSON.parse(jsonStr), null, 4);
  661. _initElements();
  662. jfPre.html(htmlspecialchars(cachedJsonString));
  663. // 获取Worker实例
  664. let worker = _getWorkerInstance();
  665. if (worker) {
  666. // 设置消息处理程序
  667. worker.onmessage = function (evt) {
  668. let msg = evt.data;
  669. switch (msg[0]) {
  670. case 'FORMATTING':
  671. formattingMsg.show();
  672. break;
  673. case 'FORMATTED':
  674. formattingMsg.hide();
  675. jfContent.html(msg[1]);
  676. _buildOptionBar();
  677. // 事件绑定
  678. _addEvents();
  679. // 支持文件下载
  680. _downloadSupport(cachedJsonString);
  681. break;
  682. }
  683. };
  684. // 发送格式化请求
  685. worker.postMessage({
  686. jsonString: jsonStr,
  687. skin: skin
  688. });
  689. } else {
  690. // Worker创建失败,回退到同步方式
  691. formatSync(jsonStr, skin);
  692. }
  693. };
  694. // 同步的方式格式化
  695. let formatSync = function (jsonStr, skin) {
  696. cachedJsonString = JSON.stringify(JSON.parse(jsonStr), null, 4);
  697. _initElements();
  698. jfPre.html(htmlspecialchars(cachedJsonString));
  699. // 显示格式化进度
  700. formattingMsg.show();
  701. try {
  702. // 回退方案:使用简单模式直接显示格式化的JSON
  703. let formattedJson = JSON.stringify(JSON.parse(jsonStr), null, 4);
  704. jfContent.html(`<div id="formattedJson"><pre class="rootItem">${htmlspecialchars(formattedJson)}</pre></div>`);
  705. // 隐藏进度提示
  706. formattingMsg.hide();
  707. // 构建操作栏
  708. _buildOptionBar();
  709. // 事件绑定
  710. _addEvents();
  711. // 支持文件下载
  712. _downloadSupport(cachedJsonString);
  713. return;
  714. } catch (e) {
  715. jfContent.html(`<div class="error">JSON格式化失败: ${e.message}</div>`);
  716. // 隐藏进度提示
  717. formattingMsg.hide();
  718. }
  719. };
  720. return {
  721. format: format,
  722. formatSync: formatSync
  723. }
  724. })();