format-lib.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782
  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. str = str.replace(/\\/g, '&#92;');
  121. return str;
  122. };
  123. /**
  124. * 直接下载,能解决中文乱码
  125. * @param content
  126. * @private
  127. */
  128. let _downloadSupport = function (content) {
  129. // 下载链接
  130. let dt = (new Date()).format('yyyyMMddHHmmss');
  131. let blob = new Blob([content], {type: 'application/octet-stream'});
  132. let button = $('<button class="xjf-btn xjf-btn-right">下载JSON</button>').appendTo('#optionBar');
  133. if (typeof chrome === 'undefined' || !chrome.permissions) {
  134. button.click(function (e) {
  135. let aLink = $('#aLinkDownload');
  136. if (!aLink[0]) {
  137. aLink = $('<a id="aLinkDownload" target="_blank" title="保存到本地">下载JSON数据</a>').appendTo('body');
  138. aLink.attr('download', 'FeHelper-' + dt + '.json');
  139. aLink.attr('href', URL.createObjectURL(blob));
  140. }
  141. aLink[0].click();
  142. });
  143. } else {
  144. button.click(function (e) {
  145. // 请求权限
  146. chrome.permissions.request({
  147. permissions: ['downloads']
  148. }, (granted) => {
  149. if (granted) {
  150. chrome.downloads.download({
  151. url: URL.createObjectURL(blob),
  152. saveAs: true,
  153. conflictAction: 'overwrite',
  154. filename: 'FeHelper-' + dt + '.json'
  155. });
  156. } else {
  157. toast('必须接受授权,才能正常下载!');
  158. }
  159. });
  160. });
  161. }
  162. };
  163. /**
  164. * chrome 下复制到剪贴板
  165. * @param text
  166. */
  167. let _copyToClipboard = function (text) {
  168. let input = document.createElement('textarea');
  169. input.style.position = 'fixed';
  170. input.style.opacity = 0;
  171. input.value = text;
  172. document.body.appendChild(input);
  173. input.select();
  174. document.execCommand('Copy');
  175. document.body.removeChild(input);
  176. toast('Json片段复制成功,随处粘贴可用!')
  177. };
  178. /**
  179. * 从el中获取json文本
  180. * @param el
  181. * @returns {string}
  182. */
  183. let getJsonText = function (el) {
  184. let txt = el.text().replace(/复制\|下载\|删除/gm,'').replace(/":\s/gm, '":').replace(/,$/, '').trim();
  185. if (!(/^{/.test(txt) && /\}$/.test(txt)) && !(/^\[/.test(txt) && /\]$/.test(txt))) {
  186. txt = '{' + txt + '}';
  187. }
  188. try {
  189. txt = JSON.stringify(JSON.parse(txt), null, 4);
  190. } catch (err) {
  191. }
  192. return txt;
  193. };
  194. // 添加json路径
  195. let _showJsonPath = function (curEl) {
  196. let keys = [];
  197. let current = curEl;
  198. // 处理当前节点
  199. if (current.hasClass('item') && !current.hasClass('rootItem')) {
  200. if (current.hasClass('item-array-element')) {
  201. // 这是数组元素,使用data-array-index属性
  202. let index = current.attr('data-array-index');
  203. if (index !== undefined) {
  204. keys.unshift('[' + index + ']');
  205. }
  206. } else {
  207. // 这是对象属性,获取key
  208. let keyText = current.find('>.key').text();
  209. if (keyText) {
  210. keys.unshift(keyText);
  211. }
  212. }
  213. }
  214. // 向上遍历所有祖先节点
  215. current.parents('.item').each(function() {
  216. let $this = $(this);
  217. // 跳过根节点
  218. if ($this.hasClass('rootItem')) {
  219. return false; // 终止遍历
  220. }
  221. if ($this.hasClass('item-array-element')) {
  222. // 这是数组元素,使用data-array-index属性
  223. let index = $this.attr('data-array-index');
  224. if (index !== undefined) {
  225. keys.unshift('[' + index + ']');
  226. }
  227. } else if ($this.hasClass('item-object') || $this.hasClass('item-array')) {
  228. // 这是容器节点,寻找它的key
  229. let $container = $this.parent().parent(); // 跳过 .kv-list
  230. if ($container.length && !$container.hasClass('rootItem')) {
  231. if ($container.hasClass('item-array-element')) {
  232. // 容器本身是数组元素
  233. let index = $container.attr('data-array-index');
  234. if (index !== undefined) {
  235. keys.unshift('[' + index + ']');
  236. }
  237. } else {
  238. // 容器是对象属性
  239. let keyText = $container.find('>.key').text();
  240. if (keyText) {
  241. keys.unshift(keyText);
  242. }
  243. }
  244. }
  245. } else {
  246. // 普通item节点,获取key
  247. let keyText = $this.find('>.key').text();
  248. if (keyText) {
  249. keys.unshift(keyText);
  250. }
  251. }
  252. });
  253. // 过滤掉空值和无效的key
  254. let validKeys = keys.filter(key => key && key.trim() !== '');
  255. // 构建路径
  256. let path = '';
  257. for (let i = 0; i < validKeys.length; i++) {
  258. let key = validKeys[i];
  259. if (key.startsWith('[') && key.endsWith(']')) {
  260. // 数组索引,直接拼接
  261. path += key;
  262. } else {
  263. // 对象属性
  264. if (i > 0) {
  265. path += '.';
  266. }
  267. path += key;
  268. }
  269. }
  270. let jfPath = $('#jsonPath');
  271. if (!jfPath.length) {
  272. jfPath = $('<span id="jsonPath"/>').prependTo(jfStatusBar);
  273. }
  274. jfPath.html('当前节点:$.' + path);
  275. };
  276. // 给某个节点增加操作项
  277. let _addOptForItem = function (el, show) {
  278. // 下载json片段
  279. let fnDownload = function (event) {
  280. event.stopPropagation();
  281. let txt = getJsonText(el);
  282. // 下载片段
  283. let dt = (new Date()).format('yyyyMMddHHmmss');
  284. let blob = new Blob([txt], {type: 'application/octet-stream'});
  285. if (typeof chrome === 'undefined' || !chrome.permissions) {
  286. // 下载JSON的简单形式
  287. $(this).attr('download', 'FeHelper-' + dt + '.json').attr('href', URL.createObjectURL(blob));
  288. } else {
  289. // 请求权限
  290. chrome.permissions.request({
  291. permissions: ['downloads']
  292. }, (granted) => {
  293. if (granted) {
  294. chrome.downloads.download({
  295. url: URL.createObjectURL(blob),
  296. saveAs: true,
  297. conflictAction: 'overwrite',
  298. filename: 'FeHelper-' + dt + '.json'
  299. });
  300. } else {
  301. toast('必须接受授权,才能正常下载!');
  302. }
  303. });
  304. }
  305. };
  306. // 复制json片段
  307. let fnCopy = function (event) {
  308. event.stopPropagation();
  309. _copyToClipboard(getJsonText(el));
  310. };
  311. // 删除json片段
  312. let fnDel = function (event) {
  313. event.stopPropagation();
  314. if (el.parent().is('#formattedJson')) {
  315. toast('如果连最外层的Json也删掉的话,就没啥意义了哦!');
  316. return false;
  317. }
  318. toast('节点已删除成功!');
  319. el.remove();
  320. jfStatusBar && jfStatusBar.hide();
  321. };
  322. $('.boxOpt').hide();
  323. if (show) {
  324. let jfOptEl = el.children('.boxOpt');
  325. if (!jfOptEl.length) {
  326. jfOptEl = $('<b class="boxOpt">' +
  327. '<a class="opt-copy" title="复制当前选中节点的JSON数据">复制</a>|' +
  328. '<a class="opt-download" target="_blank" title="下载当前选中节点的JSON数据">下载</a>|' +
  329. '<a class="opt-del" title="删除当前选中节点的JSON数据">删除</a></b>').appendTo(el);
  330. } else {
  331. jfOptEl.show();
  332. }
  333. jfOptEl.find('a.opt-download').unbind('click').bind('click', fnDownload);
  334. jfOptEl.find('a.opt-copy').unbind('click').bind('click', fnCopy);
  335. jfOptEl.find('a.opt-del').unbind('click').bind('click', fnDel);
  336. }
  337. };
  338. // 显示当前节点的Key
  339. let _toogleStatusBar = function (curEl, show) {
  340. if (!jfStatusBar) {
  341. jfStatusBar = $('<div id="statusBar"/>').appendTo('body');
  342. }
  343. if (!show) {
  344. jfStatusBar.hide();
  345. return;
  346. } else {
  347. jfStatusBar.show();
  348. }
  349. _showJsonPath(curEl);
  350. };
  351. /**
  352. * 递归折叠所有层级的对象和数组节点
  353. * @param elements
  354. */
  355. function collapse(elements) {
  356. elements.each(function () {
  357. var el = $(this);
  358. if (el.children('.kv-list').length) {
  359. el.addClass('collapsed');
  360. // 只给没有id的节点分配唯一id,并生成注释
  361. if (!el.attr('id')) {
  362. el.attr('id', 'item' + (++lastItemIdGiven));
  363. let count = el.children('.kv-list').eq(0).children().length;
  364. let comment = count + (count === 1 ? ' item' : ' items');
  365. jfStyleEl[0].insertAdjacentHTML(
  366. 'beforeend',
  367. '\n#item' + lastItemIdGiven + '.collapsed:after{color: #aaa; content:" // ' + comment + '"}'
  368. );
  369. }
  370. // 递归对子节点继续折叠,确保所有嵌套层级都被处理
  371. collapse(el.children('.kv-list').children('.item-object, .item-block'));
  372. }
  373. });
  374. }
  375. /**
  376. * 创建几个全局操作的按钮,置于页面右上角即可
  377. * @private
  378. */
  379. let _buildOptionBar = function () {
  380. let optionBar = $('#optionBar');
  381. if (optionBar.length) {
  382. optionBar.html('');
  383. } else {
  384. optionBar = $('<span id="optionBar" />').appendTo(jfContent.parent());
  385. }
  386. $('<span class="x-split">|</span>').appendTo(optionBar);
  387. let buttonFormatted = $('<button class="xjf-btn xjf-btn-left">元数据</button>').appendTo(optionBar);
  388. let buttonCollapseAll = $('<button class="xjf-btn xjf-btn-mid">折叠所有</button>').appendTo(optionBar);
  389. let plainOn = false;
  390. buttonFormatted.bind('click', function (e) {
  391. if (plainOn) {
  392. plainOn = false;
  393. jfPre.hide();
  394. jfContent.show();
  395. buttonFormatted.text('元数据');
  396. } else {
  397. plainOn = true;
  398. jfPre.show();
  399. jfContent.hide();
  400. buttonFormatted.text('格式化');
  401. }
  402. jfStatusBar && jfStatusBar.hide();
  403. });
  404. buttonCollapseAll.bind('click', function (e) {
  405. // 如果内容还没有格式化过,需要再格式化一下
  406. if (plainOn) {
  407. buttonFormatted.trigger('click');
  408. }
  409. if (buttonCollapseAll.text() === '折叠所有') {
  410. buttonCollapseAll.text('展开所有');
  411. // 递归折叠所有层级的对象和数组,确保所有内容都被折叠
  412. collapse($('#jfContent .item-object, #jfContent .item-block'));
  413. } else {
  414. buttonCollapseAll.text('折叠所有');
  415. // 展开所有内容
  416. $('.item-object,.item-block').removeClass('collapsed');
  417. }
  418. jfStatusBar && jfStatusBar.hide();
  419. });
  420. };
  421. // 附加操作
  422. let _addEvents = function () {
  423. // 折叠、展开
  424. $('#jfContent span.expand').bind('click', function (ev) {
  425. ev.preventDefault();
  426. ev.stopPropagation();
  427. let parentEl = $(this).parent();
  428. parentEl.toggleClass('collapsed');
  429. if (parentEl.hasClass('collapsed')) {
  430. collapse(parentEl);
  431. }
  432. });
  433. // 点击选中:高亮
  434. $('#jfContent .item').bind('click', function (e) {
  435. let el = $(this);
  436. if (el.hasClass('x-selected')) {
  437. _toogleStatusBar(el, false);
  438. _addOptForItem(el, false);
  439. el.removeClass('x-selected');
  440. e.stopPropagation();
  441. return true;
  442. }
  443. $('.x-selected').removeClass('x-selected');
  444. el.addClass('x-selected');
  445. // 显示底部状态栏
  446. _toogleStatusBar(el, true);
  447. _addOptForItem(el, true);
  448. if (!$(e.target).is('.item .expand')) {
  449. e.stopPropagation();
  450. } else {
  451. $(e.target).parent().trigger('click');
  452. }
  453. // 触发钩子
  454. if (typeof window._OnJsonItemClickByFH === 'function') {
  455. window._OnJsonItemClickByFH(getJsonText(el));
  456. }
  457. });
  458. // 行悬停效果:只高亮当前直接悬停的item,避免嵌套冒泡
  459. let currentHoverElement = null;
  460. $('#jfContent .item').bind('mouseenter', function (e) {
  461. // 只处理视觉效果,不触发任何其他逻辑
  462. // 清除之前的悬停样式
  463. if (currentHoverElement) {
  464. currentHoverElement.removeClass('fh-hover');
  465. }
  466. // 添加当前悬停样式
  467. let el = $(this);
  468. el.addClass('fh-hover');
  469. currentHoverElement = el;
  470. // 严格阻止事件冒泡和默认行为
  471. e.stopPropagation();
  472. e.stopImmediatePropagation();
  473. e.preventDefault();
  474. });
  475. $('#jfContent .item').bind('mouseleave', function (e) {
  476. // 只处理视觉效果,不触发任何其他逻辑
  477. let el = $(this);
  478. el.removeClass('fh-hover');
  479. // 如果当前移除的元素是记录的悬停元素,清空记录
  480. if (currentHoverElement && currentHoverElement[0] === el[0]) {
  481. currentHoverElement = null;
  482. }
  483. // 严格阻止事件冒泡和默认行为
  484. e.stopPropagation();
  485. e.stopImmediatePropagation();
  486. });
  487. // 为整个jfContent区域添加鼠标离开事件,确保彻底清除悬停样式
  488. $('#jfContent').bind('mouseleave', function (e) {
  489. if (currentHoverElement) {
  490. currentHoverElement.removeClass('fh-hover');
  491. currentHoverElement = null;
  492. }
  493. });
  494. // 图片预览功能:针对所有data-is-link=1的a标签
  495. let $imgPreview = null;
  496. // 加载缓存
  497. function getImgCache() {
  498. try {
  499. return JSON.parse(sessionStorage.getItem('fehelper-img-preview-cache') || '{}');
  500. } catch (e) { return {}; }
  501. }
  502. function setImgCache(url, isImg) {
  503. let cache = getImgCache();
  504. cache[url] = isImg;
  505. sessionStorage.setItem('fehelper-img-preview-cache', JSON.stringify(cache));
  506. }
  507. $('#jfContent').on('mouseenter', 'a[data-is-link="1"]', function(e) {
  508. const url = $(this).attr('data-link-url');
  509. if (!url) return;
  510. let cache = getImgCache();
  511. if (cache.hasOwnProperty(url)) {
  512. if (cache[url]) {
  513. $imgPreview = getOrCreateImgPreview();
  514. $imgPreview.find('img').attr('src', url);
  515. $imgPreview.show();
  516. $(document).on('mousemove.fhimg', function(ev) {
  517. $imgPreview.css({
  518. left: ev.pageX + 20 + 'px',
  519. top: ev.pageY + 20 + 'px'
  520. });
  521. });
  522. $imgPreview.css({
  523. left: e.pageX + 20 + 'px',
  524. top: e.pageY + 20 + 'px'
  525. });
  526. }
  527. return;
  528. }
  529. // 创建图片对象尝试加载
  530. const img = new window.Image();
  531. img.src = url;
  532. img.onload = function() {
  533. setImgCache(url, true);
  534. $imgPreview = getOrCreateImgPreview();
  535. $imgPreview.find('img').attr('src', url);
  536. $imgPreview.show();
  537. $(document).on('mousemove.fhimg', function(ev) {
  538. $imgPreview.css({
  539. left: ev.pageX + 20 + 'px',
  540. top: ev.pageY + 20 + 'px'
  541. });
  542. });
  543. $imgPreview.css({
  544. left: e.pageX + 20 + 'px',
  545. top: e.pageY + 20 + 'px'
  546. });
  547. };
  548. img.onerror = function() {
  549. setImgCache(url, false);
  550. };
  551. }).on('mouseleave', 'a[data-is-link="1"]', function(e) {
  552. if ($imgPreview) $imgPreview.hide();
  553. $(document).off('mousemove.fhimg');
  554. });
  555. // 新增:全局监听,防止浮窗残留
  556. $(document).on('mousemove.fhimgcheck', function(ev) {
  557. let $target = $(ev.target).closest('a[data-is-link="1"]');
  558. if ($target.length === 0) {
  559. if ($imgPreview) $imgPreview.hide();
  560. $(document).off('mousemove.fhimg');
  561. }
  562. });
  563. };
  564. /**
  565. * 初始化或获取Worker实例(异步,兼容Chrome/Edge/Firefox)
  566. * @returns {Promise<Worker|null>}
  567. */
  568. let _getWorkerInstance = async function() {
  569. if (workerInstance) {
  570. return workerInstance;
  571. }
  572. let workerUrl = chrome.runtime.getURL('json-format/json-worker.js');
  573. // 判断是否为Firefox
  574. const isFirefox = typeof InstallTrigger !== 'undefined' || navigator.userAgent.includes('Firefox');
  575. try {
  576. if (isFirefox) {
  577. workerInstance = new Worker(workerUrl);
  578. return workerInstance;
  579. } else {
  580. // Chrome/Edge用fetch+Blob方式
  581. const resp = await fetch(workerUrl);
  582. const workerScript = await resp.text();
  583. const blob = new Blob([workerScript], { type: 'application/javascript' });
  584. const blobUrl = URL.createObjectURL(blob);
  585. workerInstance = new Worker(blobUrl);
  586. return workerInstance;
  587. }
  588. } catch (e) {
  589. console.error('创建Worker失败:', e);
  590. workerInstance = null;
  591. return null;
  592. }
  593. };
  594. /**
  595. * 执行代码格式化
  596. * 支持异步worker
  597. */
  598. let format = async function (jsonStr, skin) {
  599. cachedJsonString = JSON.stringify(JSON.parse(jsonStr), null, 4);
  600. _initElements();
  601. jfPre.html(htmlspecialchars(cachedJsonString));
  602. try {
  603. // 获取Worker实例(异步)
  604. let worker = await _getWorkerInstance();
  605. if (worker) {
  606. // 设置消息处理程序
  607. worker.onmessage = function (evt) {
  608. let msg = evt.data;
  609. switch (msg[0]) {
  610. case 'FORMATTING':
  611. formattingMsg.show();
  612. break;
  613. case 'FORMATTED':
  614. formattingMsg.hide();
  615. jfContent.html(msg[1]);
  616. _buildOptionBar();
  617. // 事件绑定
  618. _addEvents();
  619. // 支持文件下载
  620. _downloadSupport(cachedJsonString);
  621. break;
  622. }
  623. };
  624. // 发送格式化请求
  625. worker.postMessage({
  626. jsonString: jsonStr,
  627. skin: skin
  628. });
  629. } else {
  630. // Worker创建失败,回退到同步方式
  631. formatSync(jsonStr, skin);
  632. }
  633. } catch (e) {
  634. console.error('Worker处理失败:', e);
  635. // 出现任何错误,回退到同步方式
  636. formatSync(jsonStr, skin);
  637. }
  638. };
  639. // 同步的方式格式化
  640. let formatSync = function (jsonStr, skin) {
  641. cachedJsonString = JSON.stringify(JSON.parse(jsonStr), null, 4);
  642. _initElements();
  643. jfPre.html(htmlspecialchars(cachedJsonString));
  644. // 显示格式化进度
  645. formattingMsg.show();
  646. try {
  647. // 回退方案:使用简单模式直接显示格式化的JSON
  648. let formattedJson = JSON.stringify(JSON.parse(jsonStr), null, 4);
  649. jfContent.html(`<div id="formattedJson"><pre class="rootItem">${htmlspecialchars(formattedJson)}</pre></div>`);
  650. // 隐藏进度提示
  651. formattingMsg.hide();
  652. // 构建操作栏
  653. _buildOptionBar();
  654. // 事件绑定
  655. _addEvents();
  656. // 支持文件下载
  657. _downloadSupport(cachedJsonString);
  658. return;
  659. } catch (e) {
  660. jfContent.html(`<div class="error">JSON格式化失败: ${e.message}</div>`);
  661. // 隐藏进度提示
  662. formattingMsg.hide();
  663. }
  664. };
  665. // 工具函数:获取或创建唯一图片预览浮窗节点
  666. function getOrCreateImgPreview() {
  667. let $img = $('#fh-img-preview');
  668. if (!$img.length) {
  669. $img = $('<div id="fh-img-preview" style="position:absolute;z-index:999999;border:1px solid #ccc;background:#fff;padding:4px;box-shadow:0 2px 8px #0002;pointer-events:none;"><img style="max-width:300px;max-height:200px;display:block;"></div>').appendTo('body');
  670. }
  671. return $img;
  672. }
  673. return {
  674. format: format,
  675. formatSync: formatSync
  676. }
  677. })();