OSS JSON Viewer Pro

专业级OSS文件查看器

  1. // ==UserScript==
  2. // @name OSS JSON Viewer Pro
  3. // @namespace http://your-namespace.com
  4. // @version 5.1
  5. // @description 专业级OSS文件查看器
  6. // @match *://*.aliyuncs.com/*
  7. // @grant GM_xmlhttpRequest
  8. // @grant GM_addStyle
  9. // @run-at document-start
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. // 立即停止原始加载
  17. window.stop();
  18. document.documentElement.innerHTML = '';
  19.  
  20. // 创建容器
  21. const container = document.createElement('div');
  22. container.id = 'json-viewer-pro';
  23. document.documentElement.appendChild(container);
  24.  
  25. // 加载动画
  26. container.innerHTML = `<div class="loader"></div>`;
  27.  
  28. // 样式注入
  29. GM_addStyle(`
  30. #json-viewer-pro {
  31. padding: 20px;
  32. font-family: 'Consolas', monospace;
  33. background: #1e1e1e;
  34. color: #d4d4d4;
  35. min-height: 100vh;
  36. }
  37. .json-key {
  38. color: #9cdcfe;
  39. font-weight: bold;
  40. }
  41. .json-string {
  42. color: #ce9178;
  43. background: rgba(206,145,120,0.1);
  44. padding: 2px 4px;
  45. border-radius: 3px;
  46. }
  47. .json-number {
  48. color: #b5cea8;
  49. font-weight: bold;
  50. }
  51. .json-boolean {
  52. color: #569cd6;
  53. font-style: italic;
  54. }
  55. .json-null {
  56. color: #808080;
  57. font-style: italic;
  58. }
  59. .loader {
  60. border: 4px solid #f3f3f3;
  61. border-top: 4px solid #3498db;
  62. border-radius: 50%;
  63. width: 40px;
  64. height: 40px;
  65. animation: spin 2s linear infinite;
  66. margin: 20px auto;
  67. }
  68. @keyframes spin {
  69. 0% { transform: rotate(0deg); }
  70. 100% { transform: rotate(360deg); }
  71. }
  72. .json-item {
  73. margin: 4px 0;
  74. padding-left: 20px;
  75. position: relative;
  76. border-left: 1px solid rgba(255,255,255,0.1);
  77. }
  78. .json-item::before {
  79. content: '';
  80. position: absolute;
  81. left: 0;
  82. top: 0;
  83. width: 12px;
  84. height: 100%;
  85. border-left: 1px solid rgba(255,255,255,0.1);
  86. }
  87. .json-item:hover {
  88. background: rgba(255,255,255,0.03);
  89. }
  90. .json-collapse {
  91. cursor: pointer;
  92. margin-right: 5px;
  93. }
  94. .json-collapse::after {
  95. content: '▼';
  96. font-size: 10px;
  97. color: #9cdcfe;
  98. margin-right: 5px;
  99. }
  100. .json-collapse.collapsed::after {
  101. content: '▶';
  102. }
  103. .json-count {
  104. color: #888;
  105. font-size: 0.9em;
  106. margin-left: 5px;
  107. }
  108. .json-image {
  109. max-width: 100%;
  110. max-height: 200px;
  111. margin: 5px 0;
  112. border-radius: 3px;
  113. box-shadow: 0 0 5px rgba(0,0,0,0.2);
  114. }
  115. .json-text {
  116. white-space: pre-wrap;
  117. background: rgba(255,255,255,0.05);
  118. padding: 5px;
  119. border-radius: 3px;
  120. margin: 5px 0;
  121. }
  122. .json-content {
  123. margin-left: 8px;
  124. padding-left: 12px;
  125. border-left: 1px dashed rgba(255,255,255,0.1);
  126. }
  127. button:hover {
  128. background: #1177bb !important;
  129. }
  130. button:active {
  131. background: #0e5389 !important;
  132. }
  133. `);
  134.  
  135. // 获取数据
  136. GM_xmlhttpRequest({
  137. method: 'GET',
  138. url: location.href,
  139. responseType: 'arraybuffer',
  140. onload: (res) => {
  141. container.innerHTML = '';
  142. try {
  143. const decoder = new TextDecoder('utf-8');
  144. const jsonData = JSON.parse(decoder.decode(res.response));
  145. renderJSON(jsonData);
  146. } catch(e) {
  147. container.innerHTML = `<div class="error">数据解析失败: ${e.message}</div>`;
  148. }
  149. },
  150. onerror: (err) => {
  151. container.innerHTML = `<div class="error">请求失败: ${err.statusText}</div>`;
  152. }
  153. });
  154.  
  155. function addControlButtons(container, data) {
  156. const buttonStyle = `
  157. padding: 6px 12px;
  158. margin-right: 10px;
  159. background: #0e639c;
  160. color: white;
  161. border: none;
  162. border-radius: 4px;
  163. cursor: pointer;
  164. font-size: 12px;
  165. `;
  166.  
  167. const buttonContainer = document.createElement('div');
  168. buttonContainer.style.marginBottom = '20px';
  169.  
  170. // 复制按钮
  171. const copyButton = document.createElement('button');
  172. copyButton.textContent = '复制 JSON';
  173. copyButton.style.cssText = buttonStyle;
  174. copyButton.onclick = () => {
  175. navigator.clipboard.writeText(JSON.stringify(data, null, 2))
  176. .then(() => {
  177. copyButton.textContent = '已复制!';
  178. setTimeout(() => copyButton.textContent = '复制 JSON', 2000);
  179. })
  180. .catch(err => alert('复制失败: ' + err));
  181. };
  182.  
  183. // 展开全部按钮
  184. const expandButton = document.createElement('button');
  185. expandButton.textContent = '展开全部';
  186. expandButton.style.cssText = buttonStyle;
  187. expandButton.onclick = () => {
  188. container.querySelectorAll('.json-content').forEach(content => {
  189. content.style.display = '';
  190. });
  191. container.querySelectorAll('.json-collapse').forEach(collapse => {
  192. collapse.classList.remove('collapsed');
  193. });
  194. };
  195.  
  196. // 折叠全部按钮
  197. const collapseButton = document.createElement('button');
  198. collapseButton.textContent = '折叠全部';
  199. collapseButton.style.cssText = buttonStyle;
  200. collapseButton.onclick = () => {
  201. container.querySelectorAll('.json-content').forEach(content => {
  202. content.style.display = 'none';
  203. });
  204. container.querySelectorAll('.json-collapse').forEach(collapse => {
  205. collapse.classList.add('collapsed');
  206. });
  207. };
  208.  
  209. buttonContainer.appendChild(copyButton);
  210. buttonContainer.appendChild(expandButton);
  211. buttonContainer.appendChild(collapseButton);
  212.  
  213. return buttonContainer;
  214. }
  215.  
  216. function renderJSON(data) {
  217. const countChildren = (obj) => {
  218. if (!obj || typeof obj !== 'object') return 0;
  219. return Object.keys(obj).length;
  220. };
  221.  
  222. const render = (obj, indent = 0) => {
  223. if (Array.isArray(obj)) {
  224. return obj.map(item => {
  225. if (typeof item === 'object' && item !== null) {
  226. return `
  227. <div class="json-item">
  228. {
  229. ${renderObject(item, indent + 1)}
  230. }
  231. </div>
  232. `;
  233. }
  234. return `
  235. <div class="json-item">
  236. ${formatValue(item)}
  237. </div>
  238. `;
  239. }).join('');
  240. }
  241. return renderObject(obj, indent);
  242. };
  243.  
  244. const renderObject = (obj, indent = 0) => {
  245. return Object.entries(obj).map(([key, value]) => {
  246. const indentStr = '&nbsp;'.repeat(indent * 4);
  247. const count = countChildren(value);
  248.  
  249. if (typeof value === 'object' && value !== null) {
  250. const isArray = Array.isArray(value);
  251. return `
  252. <div class="json-item" data-json='${JSON.stringify(value)}'>
  253. <span class="json-collapse"></span>
  254. <span class="json-key">"${key}"</span>:
  255. ${isArray ?
  256. `[<span class="json-count">${count} items</span>` :
  257. `{<span class="json-count">${count} keys</span>`}
  258. <div class="json-content">
  259. ${render(value, indent + 1)}
  260. </div>
  261. ${isArray ? ']' : '}'},
  262. </div>
  263. `;
  264. }
  265.  
  266. // Handle image URLs
  267. if (typeof value === 'string' &&
  268. (value.match(/\.(jpeg|jpg|gif|png)$/) ||
  269. value.startsWith('data:image/'))) {
  270. return `
  271. <div class="json-item">
  272. <span class="json-key">"${key}"</span>:
  273. <img src="${value}" class="json-image" alt="${key} image"/>
  274. </div>
  275. `;
  276. }
  277.  
  278. // Handle multi-line text
  279. if (typeof value === 'string' && value.includes('\n')) {
  280. return `
  281. <div class="json-item">
  282. <span class="json-key">"${key}"</span>:
  283. <div class="json-text">${value}</div>
  284. </div>
  285. `;
  286. }
  287.  
  288. return `
  289. <div class="json-item">
  290. <span class="json-key">"${key}"</span>:
  291. ${formatValue(value)},
  292. </div>
  293. `;
  294. }).join('');
  295. };
  296.  
  297. const formatValue = (value) => {
  298. if (typeof value === 'string') {
  299. // Skip formatting if it's an image URL or multi-line text
  300. if (value.match(/\.(jpeg|jpg|gif|png)$/) ||
  301. value.startsWith('data:image/') ||
  302. value.includes('\n')) {
  303. return '';
  304. }
  305. return `<span class="json-string">"${value}"</span>`;
  306. }
  307. if (typeof value === 'number') return `<span class="json-number">${value}</span>`;
  308. if (typeof value === 'boolean') return `<span class="json-boolean">${value}</span>`;
  309. if (value === null) return `<span class="json-null">null</span>`;
  310. return '';
  311. };
  312.  
  313. container.innerHTML = '';
  314. container.appendChild(addControlButtons(container, data));
  315. const jsonContainer = document.createElement('div');
  316. jsonContainer.style.marginTop = '20px';
  317. jsonContainer.innerHTML = render(data);
  318. container.appendChild(jsonContainer);
  319.  
  320. // 修改折叠功能
  321. document.querySelectorAll('.json-collapse').forEach(collapse => {
  322. collapse.addEventListener('click', function() {
  323. const parent = this.parentElement;
  324. const content = parent.querySelector('.json-content');
  325. const countSpan = parent.querySelector('.json-count');
  326. const jsonData = JSON.parse(parent.getAttribute('data-json'));
  327. const isArray = Array.isArray(jsonData);
  328.  
  329. if (content.style.display === 'none') {
  330. content.style.display = '';
  331. countSpan.textContent = isArray ?
  332. `${jsonData.length} items` :
  333. `${Object.keys(jsonData).length} keys`;
  334. } else {
  335. content.style.display = 'none';
  336. countSpan.textContent = isArray ?
  337. `${jsonData.length} items` :
  338. `${Object.keys(jsonData).length} keys`;
  339. }
  340.  
  341. this.classList.toggle('collapsed');
  342. });
  343. });
  344. }
  345. })();

QingJ © 2025

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