前端技术(34) : json解析对象数组转换表格展示

来源 : 豆包

1.效果图

2.HTML

<!DOCTYPE html>
<html lang="zh-CN">
	<head>
		<meta charset="UTF-8">
		<meta name="viewport" content="width=device-width, initial-scale=1.0">
		<title>JSON解析与表格展示工具</title>
		<script src="https://cdn.tailwindcss.com"></script>
		<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
		<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
		<script>
			tailwind.config = {
				theme: {
					extend: {
						colors: {
							primary: '#3b82f6',
							secondary: '#64748b',
							accent: '#06b6d4',
							success: '#10b981',
							warning: '#f59e0b',
							danger: '#ef4444',
							info: '#3b82f6',
							light: '#f8fafc',
							dark: '#1e293b',
						},
						fontFamily: {
							inter: ['Inter', 'system-ui', 'sans-serif'],
						},
					},
				}
			}
		</script>
		<style>
			.dark{
				background-color: '#1e293b';	  
			}
		</style>
		<style type="text/tailwindcss">
			@layer utilities {
      .content-auto {
        content-visibility: auto;
      }
      .scrollbar-hide {
        -ms-overflow-style: none;
        scrollbar-width: none;
      }
      .scrollbar-hide::-webkit-scrollbar {
        display: none;
      }
      .json-editor {
        font-family: Consolas, Monaco, 'Andale Mono', monospace;
        font-size: 0.875rem;
      }
      .expression-preview {
        font-family: Consolas, Monaco, 'Andale Mono', monospace;
      }
    }
  </style>
	</head>
	<body class="bg-gray-50 font-inter min-h-screen flex flex-col">
		<!-- 导航栏 -->
		<nav class="bg-white shadow-md sticky top-0 z-50 transition-all duration-300">
			<div class="container mx-auto px-4 py-3 flex justify-between items-center">
				<div class="flex items-center space-x-2">
					<i class="fa fa-code text-primary text-2xl"></i>
					<h1 class="text-xl font-bold text-gray-800">JSON解析与表格展示工具</h1>
				</div>
				<div class="flex items-center space-x-4">
					<button id="theme-toggle" class="p-2 rounded-full hover:bg-gray-100 transition-colors" style="display: none;">
						<i class="fa fa-moon-o text-gray-600"></i>
					</button>
					<button id="help-btn" class="p-2 rounded-full hover:bg-gray-100 transition-colors">
						<i class="fa fa-question-circle text-gray-600"></i>
					</button>
				</div>
			</div>
		</nav>

		<!-- 主内容区 -->
		<main class="flex-grow container mx-auto px-4 py-6">
			<!-- 功能区 -->
			<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
				<!-- JSON输入区 -->
				<div
					class="lg:col-span-2 bg-white rounded-xl shadow-lg p-5 transition-all duration-300 hover:shadow-xl">
					<div class="flex justify-between items-center mb-4">
						<h2 class="text-lg font-semibold text-gray-800 flex items-center">
							<i class="fa fa-file-code-o text-primary mr-2"></i>JSON输入
						</h2>
						<div class="flex space-x-2">
							<button id="format-json"
								class="px-3 py-1 bg-primary text-white rounded-md hover:bg-primary/90 transition-colors flex items-center text-sm">
								<i class="fa fa-indent mr-1"></i>格式化
							</button>
							<button id="clear-json"
								class="px-3 py-1 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition-colors flex items-center text-sm">
								<i class="fa fa-trash-o mr-1"></i>清空
							</button>
						</div>
					</div>
					<div class="relative">
						<textarea id="json-input"
							class="w-full h-64 p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary/50 focus:border-primary transition-all json-editor resize-none"
							placeholder='粘贴JSON数据 (例如: {"users": [{"name": "张三", "age": 30}, {"name": "李四", "age": 25}]})...'></textarea>
						<div id="json-error"
							class="hidden absolute bottom-3 right-3 bg-danger/10 border border-danger/30 text-danger px-3 py-1 rounded-md text-sm flex items-center">
							<i class="fa fa-exclamation-circle mr-1"></i>
							<span id="error-message">JSON格式错误</span>
						</div>
					</div>
				</div>

				<!-- 表达式与操作区 -->
				<div class="bg-white rounded-xl shadow-lg p-5 transition-all duration-300 hover:shadow-xl">
					<div class="flex justify-between items-center mb-4">
						<h2 class="text-lg font-semibold text-gray-800 flex items-center">
							<i class="fa fa-filter text-primary mr-2"></i>解析表达式
						</h2>
						<button id="expression-help" class="p-1.5 rounded-full hover:bg-gray-100 transition-colors">
							<i class="fa fa-question-circle text-gray-500 text-sm"></i>
						</button>
					</div>
					<div class="mb-4">
						<div class="relative">
							<input type="text" id="expression"
								class="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary/50 focus:border-primary transition-all expression-preview"
								placeholder="输入解析表达式 (例如: $.users)">
							<div id="expression-preview"
								class="absolute top-full left-0 right-0 mt-1 p-2 bg-gray-50 rounded-md border border-gray-200 text-sm opacity-0 invisible transition-all duration-200 z-10">
								结果预览: <span id="preview-result">暂无数据</span>
							</div>
						</div>
						<p class="text-xs text-gray-500 mt-1.5">支持JSONPath语法,例如 <code>$.users</code> 或
							<code>$.data[?(@.age > 25)]</code>
						</p>
					</div>

					<div class="space-y-3">
						<button id="parse-btn"
							class="w-full py-2.5 bg-primary text-white rounded-lg hover:bg-primary/90 transition-colors flex items-center justify-center">
							<i class="fa fa-magic mr-2"></i>解析并展示表格
						</button>

						<div class="grid grid-cols-2 gap-3">
							<button id="save-config"
								class="py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors flex items-center justify-center text-sm">
								<i class="fa fa-save mr-1.5"></i>保存配置
							</button>
							<button id="load-example"
								class="py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors flex items-center justify-center text-sm">
								<i class="fa fa-lightbulb-o mr-1.5"></i>加载示例
							</button>
						</div>
					</div>

					<div class="mt-5 pt-4 border-t border-gray-100">
						<h3 class="font-medium text-gray-700 mb-2">数据统计</h3>
						<div class="grid grid-cols-2 gap-3 text-sm">
							<div class="bg-gray-50 p-3 rounded-lg">
								<div class="text-gray-500 mb-1">总行数</div>
								<div id="row-count" class="font-semibold text-gray-800">0</div>
							</div>
							<div class="bg-gray-50 p-3 rounded-lg">
								<div class="text-gray-500 mb-1">总列数</div>
								<div id="column-count" class="font-semibold text-gray-800">0</div>
							</div>
						</div>
					</div>
				</div>
			</div>

			<!-- 结果展示区 -->
			<div class="bg-white rounded-xl shadow-lg p-5 transition-all duration-300 hover:shadow-xl mb-8">
				<div class="flex justify-between items-center mb-4">
					<h2 class="text-lg font-semibold text-gray-800 flex items-center">
						<i class="fa fa-table text-primary mr-2"></i>解析结果
					</h2>
					<div class="flex space-x-2">
						<button id="export-csv"
							class="px-3 py-1 bg-success text-white rounded-md hover:bg-success/90 transition-colors flex items-center text-sm">
							<i class="fa fa-download mr-1"></i>导出CSV
						</button>
						<button id="expand-all"
							class="px-3 py-1 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition-colors flex items-center text-sm">
							<i class="fa fa-plus-square-o mr-1"></i>展开所有
						</button>
						<button id="collapse-all"
							class="px-3 py-1 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition-colors flex items-center text-sm">
							<i class="fa fa-minus-square-o mr-1"></i>收起所有
						</button>
					</div>
				</div>

				<div id="table-container" class="overflow-x-auto">
					<table id="result-table" class="min-w-full divide-y divide-gray-200">
						<thead class="bg-gray-50">
							<tr id="table-header"></tr>
						</thead>
						<tbody id="table-body" class="bg-white divide-y divide-gray-200"></tbody>
					</table>
				</div>

				<div id="no-data-message" class="py-16 flex flex-col items-center justify-center text-gray-500">
					<i class="fa fa-file-o text-4xl mb-3 text-gray-300"></i>
					<p>请输入JSON数据并解析</p>
				</div>

				<div id="loading-indicator"
					class="py-16 flex flex-col items-center justify-center text-gray-500 hidden">
					<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-primary"></div>
					<p class="mt-3">正在解析数据...</p>
				</div>
			</div>

			<!-- 图表分析区 -->
			<div class="bg-white rounded-xl shadow-lg p-5 transition-all duration-300 hover:shadow-xl">
				<div class="flex justify-between items-center mb-4">
					<h2 class="text-lg font-semibold text-gray-800 flex items-center">
						<i class="fa fa-bar-chart text-primary mr-2"></i>数据可视化
					</h2>
					<div class="flex space-x-2">
						<select id="chart-type"
							class="px-3 py-1 border border-gray-300 rounded-md focus:ring-2 focus:ring-primary/50 focus:border-primary transition-all text-sm">
							<option value="bar">柱状图</option>
							<option value="pie">饼图</option>
							<option value="line">折线图</option>
						</select>
						<select id="chart-column"
							class="px-3 py-1 border border-gray-300 rounded-md focus:ring-2 focus:ring-primary/50 focus:border-primary transition-all text-sm">
							<option value="">请选择列</option>
						</select>
					</div>
				</div>

				<div id="chart-container" class="h-80">
					<canvas id="data-chart"></canvas>
				</div>

				<div id="no-chart-data" class="py-16 flex flex-col items-center justify-center text-gray-500">
					<i class="fa fa-pie-chart text-4xl mb-3 text-gray-300"></i>
					<p>请解析数据后选择列进行可视化</p>
				</div>
			</div>
		</main>

		<!-- 页脚 -->
		<footer class="bg-white border-t border-gray-200 py-4">
			<div class="container mx-auto px-4 text-center text-gray-500 text-sm">
				<p>JSON解析与表格展示工具 &copy; 2025</p>
			</div>
		</footer>

		<!-- 帮助模态框 -->
		<div id="help-modal"
			class="fixed inset-0 bg-black/50 flex items-center justify-center z-50 opacity-0 invisible transition-all duration-300">
			<div
				class="bg-white rounded-xl shadow-2xl max-w-2xl w-full max-h-[80vh] overflow-y-auto mx-4 transform scale-95 transition-all duration-300">
				<div class="p-5 border-b border-gray-100 flex justify-between items-center">
					<h3 class="text-lg font-semibold text-gray-800">使用帮助</h3>
					<button id="close-help" class="p-1.5 rounded-full hover:bg-gray-100 transition-colors">
						<i class="fa fa-times text-gray-500"></i>
					</button>
				</div>
				<div class="p-5">
					<div class="space-y-4">
						<div>
							<h4 class="font-medium text-gray-700 mb-1.5">1. 输入JSON数据</h4>
							<p class="text-gray-600">在左侧文本区域粘贴有效的JSON字符串。您可以使用"格式化"按钮自动整理JSON格式,或使用"清空"按钮清除当前内容。</p>
						</div>
						<div>
							<h4 class="font-medium text-gray-700 mb-1.5">2. 输入解析表达式</h4>
							<p class="text-gray-600">使用JSONPath语法输入解析表达式。例如:</p>
							<ul class="list-disc pl-5 text-gray-600 space-y-1">
								<li><code>$.users</code> - 选择根对象下的users数组</li>
								<li><code>$.data[?(@.age > 25)]</code> - 选择data数组中age大于25的所有元素</li>
								<li><code>$..name</code> - 递归选择所有name属性</li>
							</ul>
						</div>
						<div>
							<h4 class="font-medium text-gray-700 mb-1.5">3. 解析并展示</h4>
							<p class="text-gray-600">点击"解析并展示表格"按钮,系统将根据您的表达式解析JSON数据并以表格形式展示结果。</p>
						</div>
						<div>
							<h4 class="font-medium text-gray-700 mb-1.5">4. 表格操作</h4>
							<p class="text-gray-600">解析后,您可以:</p>
							<ul class="list-disc pl-5 text-gray-600 space-y-1">
								<li>使用"导出CSV"按钮将表格数据导出为CSV文件</li>
								<li>使用"展开/收起所有"按钮控制嵌套JSON的显示</li>
								<li>在数据可视化区域选择列生成图表</li>
							</ul>
						</div>
					</div>
				</div>
				<div class="p-5 border-t border-gray-100">
					<button id="close-help-btn"
						class="px-4 py-2 bg-primary text-white rounded-md hover:bg-primary/90 transition-colors">
						我知道了
					</button>
				</div>
			</div>
		</div>

		<script>
			// 全局变量
			let parsedData = null;
			let chartInstance = null;

			// DOM元素
			const jsonInput = document.getElementById('json-input');
			const expression = document.getElementById('expression');
			const parseBtn = document.getElementById('parse-btn');
			const formatJsonBtn = document.getElementById('format-json');
			const clearJsonBtn = document.getElementById('clear-json');
			const jsonError = document.getElementById('json-error');
			const errorMessage = document.getElementById('error-message');
			const resultTable = document.getElementById('result-table');
			const tableHeader = document.getElementById('table-header');
			const tableBody = document.getElementById('table-body');
			const noDataMessage = document.getElementById('no-data-message');
			const loadingIndicator = document.getElementById('loading-indicator');
			const rowCount = document.getElementById('row-count');
			const columnCount = document.getElementById('column-count');
			const exportCsvBtn = document.getElementById('export-csv');
			const expandAllBtn = document.getElementById('expand-all');
			const collapseAllBtn = document.getElementById('collapse-all');
			const chartType = document.getElementById('chart-type');
			const chartColumn = document.getElementById('chart-column');
			const dataChart = document.getElementById('data-chart');
			const chartContainer = document.getElementById('chart-container');
			const noChartData = document.getElementById('no-chart-data');
			const saveConfigBtn = document.getElementById('save-config');
			const loadExampleBtn = document.getElementById('load-example');
			const themeToggle = document.getElementById('theme-toggle');
			const helpBtn = document.getElementById('help-btn');
			const helpModal = document.getElementById('help-modal');
			const closeHelp = document.getElementById('close-help');
			const closeHelpBtn = document.getElementById('close-help-btn');
			const expressionHelp = document.getElementById('expression-help');
			const expressionPreview = document.getElementById('expression-preview');
			const previewResult = document.getElementById('preview-result');

			// 示例JSON数据
			const exampleJson = {
				"employees": [{
						"id": 1,
						"name": "张三",
						"age": 30,
						"department": "技术部",
						"position": "前端开发工程师",
						"salary": 15000,
						"skills": ["JavaScript", "HTML", "CSS", "React"],
						"performance": {
							"last_month": 4.5,
							"average": 4.2
						}
					},
					{
						"id": 2,
						"name": "李四",
						"age": 25,
						"department": "技术部",
						"position": "后端开发工程师",
						"salary": 18000,
						"skills": ["Java", "Spring", "MySQL", "Docker"],
						"performance": {
							"last_month": 4.8,
							"average": 4.6
						}
					},
					{
						"id": 3,
						"name": "王五",
						"age": 35,
						"department": "市场部",
						"position": "市场经理",
						"salary": 20000,
						"skills": ["市场营销", "数据分析", "演讲"],
						"performance": {
							"last_month": 4.0,
							"average": 4.1
						}
					},
					{
						"id": 4,
						"name": "赵六",
						"age": 28,
						"department": "设计部",
						"position": "UI设计师",
						"salary": 16000,
						"skills": ["Figma", "Adobe XD", "Photoshop"],
						"performance": {
							"last_month": 4.7,
							"average": 4.4
						}
					},
					{
						"id": 5,
						"name": "钱七",
						"age": 40,
						"department": "人力资源部",
						"position": "HR总监",
						"salary": 22000,
						"skills": ["招聘", "培训", "绩效管理"],
						"performance": {
							"last_month": 4.3,
							"average": 4.3
						}
					}
				]
			};

			// 初始化
			document.addEventListener('DOMContentLoaded', () => {
				// 加载本地存储的配置
				loadSavedConfig();

				// 添加事件监听器
				parseBtn.addEventListener('click', parseJson);
				formatJsonBtn.addEventListener('click', formatJson);
				clearJsonBtn.addEventListener('click', clearJson);
				exportCsvBtn.addEventListener('click', exportToCsv);
				expandAllBtn.addEventListener('click', expandAllRows);
				collapseAllBtn.addEventListener('click', collapseAllRows);
				chartType.addEventListener('change', updateChart);
				chartColumn.addEventListener('change', updateChart);
				saveConfigBtn.addEventListener('click', saveConfig);
				loadExampleBtn.addEventListener('click', loadExample);
				themeToggle.addEventListener('click', toggleTheme);
				helpBtn.addEventListener('click', showHelp);
				closeHelp.addEventListener('click', hideHelp);
				closeHelpBtn.addEventListener('click', hideHelp);
				expressionHelp.addEventListener('click', showExpressionHelp);

				// 表达式输入框的焦点事件
				expression.addEventListener('focus', showPreview);
				expression.addEventListener('blur', hidePreview);
				expression.addEventListener('input', updatePreview);

				// 表格行的点击事件委托
				tableBody.addEventListener('click', (e) => {
					const expandBtn = e.target.closest('.expand-btn');
					if (expandBtn) {
						const rowId = expandBtn.getAttribute('data-row-id');
						const cellIndex = expandBtn.getAttribute('data-cell-index');
						toggleNestedData(rowId, cellIndex);
					}
				});

				// 导航栏滚动效果
				window.addEventListener('scroll', () => {
					const nav = document.querySelector('nav');
					if (window.scrollY > 10) {
						nav.classList.add('py-2', 'shadow-lg');
						nav.classList.remove('py-3', 'shadow-md');
					} else {
						nav.classList.add('py-3', 'shadow-md');
						nav.classList.remove('py-2', 'shadow-lg');
					}
				});
			});

			// 解析JSON数据
			function parseJson() {
				try {
					// 显示加载指示器
					showLoading();

					// 隐藏错误信息
					jsonError.classList.add('hidden');

					// 解析JSON
					const jsonData = JSON.parse(jsonInput.value.trim() || '{}');

					// 解析表达式
					const expr = expression.value.trim() || '$';
					const result = evaluateJsonPath(jsonData, expr);
					console.log(result, expr, jsonData)

					// 处理结果
					parsedData = result;
					displayTable(result);
					updateStats(result);
					updateChartColumns(result);

					// 隐藏加载指示器
					hideLoading();

					// 显示成功消息
					showNotification('解析成功', 'success');
				} catch (error) {
					// 隐藏加载指示器
					hideLoading();

					// 显示错误信息
					jsonError.classList.remove('hidden');
					errorMessage.textContent = `解析错误: ${error.message}`;

					// 显示错误通知
					showNotification(`解析失败: ${error.message}`, 'error');
				}
			}

			// 计算JSONPath表达式
			function evaluateJsonPath(data, path) {
				// 移除开头的$符号和点号
				path = path.replace(/^\$\.?/, '');

				if (!path) return data;

				// 分割路径
				const segments = path.split('.').filter(seg => seg.trim() !== '');

				let current = data;
				for (const segment of segments) {
					// 处理数组索引
					const arrayMatch = segment.match(/(\w+)\[(\d+)\]/);
					if (arrayMatch) {
						const prop = arrayMatch[1];
						const index = parseInt(arrayMatch[2]);

						if (current[prop] === undefined || !Array.isArray(current[prop]) || index >= current[prop].length) {
							return undefined;
						}

						current = current[prop][index];
						continue;
					}

					// 处理过滤表达式 - 使用非贪婪匹配
					const filterMatch = segment.match(/(\w+)\[\?\((.*)\)\]/);
					console.log(filterMatch)
					if (filterMatch) {
						const prop = filterMatch[1];
						const condition = filterMatch[2];

						if (current[prop] === undefined || !Array.isArray(current[prop])) {
							return undefined;
						}

						// 简单的过滤条件解析
						const filtered = current[prop].filter(item => {
							try {
								// 创建一个沙箱环境来执行过滤条件
								const sandbox = {
									item
								};
								return new Function('item', `with(item) { return ${condition}; }`)(item);
							} catch (e) {
								return false;
							}
						});

						current = filtered;
						continue;
					}

					// 处理通配符
					if (segment === '*') {
						if (!Array.isArray(current)) {
							current = Object.values(current);
						}
						continue;
					}

					// 处理普通属性
					if (current[segment] === undefined) {
						return undefined;
					}

					current = current[segment];
				}

				return current;
			}


			// 显示表格
			function displayTable(data) {
				// 清空表格
				tableHeader.innerHTML = '';
				tableBody.innerHTML = '';

				// 检查数据是否为空
				if (!data || (Array.isArray(data) && data.length === 0)) {
					resultTable.classList.add('hidden');
					noDataMessage.classList.remove('hidden');
					return;
				}

				// 显示表格,隐藏无数据消息
				resultTable.classList.remove('hidden');
				noDataMessage.classList.add('hidden');

				// 处理非数组数据
				if (!Array.isArray(data)) {
					data = [data];
				}

				// 提取所有可能的列名
				const columns = new Set();
				data.forEach(row => {
					Object.keys(row).forEach(key => columns.add(key));
				});

				// 创建表头
				Array.from(columns).forEach((column, index) => {
					const th = document.createElement('th');
					th.scope = 'col';
					th.className = 'px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider';
					th.textContent = column;
					th.setAttribute('data-column-index', index);
					tableHeader.appendChild(th);
				});

				// 创建表格内容
				data.forEach((row, rowIndex) => {
					const tr = document.createElement('tr');
					tr.className = 'hover:bg-gray-50 transition-colors';
					tr.setAttribute('data-row-index', rowIndex);

					Array.from(columns).forEach((column, cellIndex) => {
						const td = document.createElement('td');
						td.className = 'px-6 py-4 whitespace-nowrap text-sm text-gray-500';

						const value = row[column];

						if (value === undefined || value === null) {
							td.textContent = '';
						} else if (typeof value === 'object') {
							// 对象或数组
							const nestedData = JSON.stringify(value);
							const isArray = Array.isArray(value);

							td.innerHTML = `
              <div class="flex items-center">
                <button class="expand-btn mr-2 text-primary hover:text-primary/80 transition-colors" data-row-id="${rowIndex}" data-cell-index="${cellIndex}">
                  <i class="fa fa-plus-square-o"></i>
                </button>
                <span>${isArray ? `[数组(${value.length})]` : '[对象]'}</span>
              </div>
              <div class="nested-data hidden mt-2 ml-6 p-3 bg-gray-50 rounded-lg border border-gray-200 text-xs">
                <pre class="json-preview">${formatJsonString(nestedData)}</pre>
              </div>
            `;
						} else {
							// 基本类型
							td.textContent = value.toString();
						}

						td.setAttribute('data-column-name', column);
						tr.appendChild(td);
					});

					tableBody.appendChild(tr);
				});
			}

			// 切换嵌套数据的显示/隐藏
			function toggleNestedData(rowId, cellIndex) {
				const expandBtn = document.querySelector(`.expand-btn[data-row-id="${rowId}"][data-cell-index="${cellIndex}"]`);
				const nestedData = expandBtn.closest('td').querySelector('.nested-data');
				const icon = expandBtn.querySelector('i');

				if (nestedData.classList.contains('hidden')) {
					// 显示嵌套数据
					nestedData.classList.remove('hidden');
					icon.classList.remove('fa-plus-square-o');
					icon.classList.add('fa-minus-square-o');
				} else {
					// 隐藏嵌套数据
					nestedData.classList.add('hidden');
					icon.classList.remove('fa-minus-square-o');
					icon.classList.add('fa-plus-square-o');
				}
			}

			// 展开所有行
			function expandAllRows() {
				document.querySelectorAll('.expand-btn').forEach(btn => {
					const nestedData = btn.closest('td').querySelector('.nested-data');
					const icon = btn.querySelector('i');

					if (nestedData && nestedData.classList.contains('hidden')) {
						nestedData.classList.remove('hidden');
						icon.classList.remove('fa-plus-square-o');
						icon.classList.add('fa-minus-square-o');
					}
				});
			}

			// 收起所有行
			function collapseAllRows() {
				document.querySelectorAll('.expand-btn').forEach(btn => {
					const nestedData = btn.closest('td').querySelector('.nested-data');
					const icon = btn.querySelector('i');

					if (nestedData && !nestedData.classList.contains('hidden')) {
						nestedData.classList.add('hidden');
						icon.classList.remove('fa-minus-square-o');
						icon.classList.add('fa-plus-square-o');
					}
				});
			}

			// 格式化JSON
			function formatJson() {
				try {
					const jsonData = JSON.parse(jsonInput.value.trim() || '{}');
					jsonInput.value = JSON.stringify(jsonData, null, 2);
					jsonError.classList.add('hidden');
				} catch (error) {
					jsonError.classList.remove('hidden');
					errorMessage.textContent = `格式化错误: ${error.message}`;
				}
			}

			// 清空JSON输入
			function clearJson() {
				jsonInput.value = '';
				jsonError.classList.add('hidden');
			}

			// 更新统计信息
			function updateStats(data) {
				if (!data) {
					rowCount.textContent = '0';
					columnCount.textContent = '0';
					return;
				}

				// 处理非数组数据
				if (!Array.isArray(data)) {
					data = [data];
				}

				// 计算行数
				rowCount.textContent = data.length;

				// 计算列数
				const columns = new Set();
				data.forEach(row => {
					Object.keys(row).forEach(key => columns.add(key));
				});
				columnCount.textContent = columns.size;
			}

			// 更新图表列选择器
			function updateChartColumns(data) {
				// 清空当前选项
				chartColumn.innerHTML = '<option value="">请选择列</option>';

				if (!data || (Array.isArray(data) && data.length === 0)) {
					chartContainer.classList.add('hidden');
					noChartData.classList.remove('hidden');
					return;
				}

				// 处理非数组数据
				if (!Array.isArray(data)) {
					data = [data];
				}

				// 获取第一行的列名
				const firstRow = data[0];
				if (!firstRow) {
					chartContainer.classList.add('hidden');
					noChartData.classList.remove('hidden');
					return;
				}

				// 添加列选项
				Object.keys(firstRow).forEach(key => {
					const option = document.createElement('option');
					option.value = key;
					option.textContent = key;
					chartColumn.appendChild(option);
				});

				// 显示图表容器
				chartContainer.classList.remove('hidden');
				noChartData.classList.add('hidden');
			}

			// 更新图表
			function updateChart() {
				const column = chartColumn.value;
				const type = chartType.value;

				if (!column || !parsedData || (Array.isArray(parsedData) && parsedData.length === 0)) {
					return;
				}

				// 处理非数组数据
				let data = parsedData;
				if (!Array.isArray(data)) {
					data = [data];
				}

				// 准备图表数据
				const chartData = {
					labels: [],
					datasets: [{
						label: column,
						data: [],
						backgroundColor: [],
						borderColor: [],
						borderWidth: 1
					}]
				};

				// 收集数据
				const valueCounts = new Map();
				const allValues = [];

				data.forEach((row, index) => {
					const value = row[column];
					if (value !== undefined && value !== null) {
						if (type === 'pie') {
							// 饼图统计每个值的出现次数
							const key = String(value);
							valueCounts.set(key, (valueCounts.get(key) || 0) + 1);
						} else {
							// 柱状图和折线图使用原始数据
							chartData.labels.push(`数据${index + 1}`);
							chartData.datasets[0].data.push(value);

							// 为柱状图生成随机颜色
							if (type === 'bar') {
								const color = getRandomColor();
								chartData.datasets[0].backgroundColor.push(color);
								chartData.datasets[0].borderColor.push(color);
							}
						}

						allValues.push(value);
					}
				});

				// 处理饼图数据
				if (type === 'pie') {
					chartData.labels = Array.from(valueCounts.keys());
					chartData.datasets[0].data = Array.from(valueCounts.values());

					// 为饼图生成颜色
					chartData.datasets[0].backgroundColor = chartData.labels.map(() => getRandomColor());
				}

				// 处理折线图数据
				if (type === 'line') {
					// 确保折线图的数据是数字类型
					chartData.datasets[0].data = chartData.datasets[0].data.map(Number);

					// 设置线条颜色
					chartData.datasets[0].borderColor = '#3b82f6';
					chartData.datasets[0].backgroundColor = 'rgba(59, 130, 246, 0.1)';
					chartData.datasets[0].fill = true;
				}

				// 销毁现有图表
				if (chartInstance) {
					chartInstance.destroy();
				}

				// 创建新图表
				chartInstance = new Chart(dataChart, {
					type: type,
					data: chartData,
					options: {
						responsive: true,
						maintainAspectRatio: false,
						plugins: {
							legend: {
								position: 'top',
							},
							tooltip: {
								mode: 'index',
								intersect: false,
							}
						},
						scales: {
							y: {
								beginAtZero: true,
								grid: {
									drawBorder: false,
								}
							},
							x: {
								grid: {
									display: false,
									drawBorder: false,
								}
							}
						}
					}
				});
			}

			// 导出为CSV
			function exportToCsv() {
				if (!parsedData || (Array.isArray(parsedData) && parsedData.length === 0)) {
					showNotification('没有数据可导出', 'warning');
					return;
				}

				// 处理非数组数据
				let data = parsedData;
				if (!Array.isArray(data)) {
					data = [data];
				}

				// 提取列名
				const columns = new Set();
				data.forEach(row => {
					Object.keys(row).forEach(key => columns.add(key));
				});

				const columnNames = Array.from(columns);

				// 创建CSV内容
				let csvContent = columnNames.join(',') + '\n';

				// 添加数据行
				data.forEach(row => {
					const values = columnNames.map(column => {
						const value = row[column];
						if (value === undefined || value === null) {
							return '';
						}

						// 处理字符串值,添加引号并转义引号
						if (typeof value === 'string') {
							return `"${value.replace(/"/g, '""')}"`;
						}

						return value;
					});

					csvContent += values.join(',') + '\n';
				});

				// 创建下载链接
				const blob = new Blob([csvContent], {
					type: 'text/csv;charset=utf-8;'
				});
				const url = URL.createObjectURL(blob);
				const link = document.createElement('a');
				link.setAttribute('href', url);
				link.setAttribute('download', 'data_export.csv');
				link.style.visibility = 'hidden';
				document.body.appendChild(link);
				link.click();
				document.body.removeChild(link);

				// 显示成功通知
				showNotification('CSV导出成功', 'success');
			}

			// 保存配置到本地存储
			function saveConfig() {
				try {
					const config = {
						json: jsonInput.value,
						expression: expression.value
					};

					localStorage.setItem('jsonParserConfig', JSON.stringify(config));
					showNotification('配置已保存', 'success');
				} catch (error) {
					showNotification('保存失败: ' + error.message, 'error');
				}
			}

			// 加载保存的配置
			function loadSavedConfig() {
				try {
					const config = localStorage.getItem('jsonParserConfig');
					if (config) {
						const {
							json,
							expression: expr
						} = JSON.parse(config);
						jsonInput.value = json;
						expression.value = expr;
					}
				} catch (error) {
					// 忽略错误
				}
			}

			// 加载示例数据
			function loadExample() {
				jsonInput.value = JSON.stringify(exampleJson, null, 2);
				expression.value = '$.employees';
				jsonError.classList.add('hidden');
				showNotification('已加载示例数据', 'info');
			}

			// 切换主题
			function toggleTheme() {
				const body = document.body;
				const icon = themeToggle.querySelector('i');

				if (body.classList.contains('dark')) {
					body.classList.remove('dark');
					icon.classList.remove('fa-sun-o');
					icon.classList.add('fa-moon-o');
				} else {
					body.classList.add('dark');
					icon.classList.remove('fa-moon-o');
					icon.classList.add('fa-sun-o');
				}
			}

			// 显示帮助
			function showHelp() {
				helpModal.classList.remove('invisible', 'opacity-0');
				helpModal.querySelector('div').classList.remove('scale-95');
				helpModal.querySelector('div').classList.add('scale-100');
			}

			// 隐藏帮助
			function hideHelp() {
				helpModal.classList.add('opacity-0');
				helpModal.querySelector('div').classList.remove('scale-100');
				helpModal.querySelector('div').classList.add('scale-95');

				setTimeout(() => {
					helpModal.classList.add('invisible');
				}, 300);
			}

			// 显示表达式帮助
			function showExpressionHelp() {
				alert(`支持的JSONPath语法:
- $.property - 选择根对象的属性
- $..property - 递归选择所有属性
- $.array[0] - 选择数组的第一个元素
- $.array[*] - 选择数组的所有元素
- $.array[?(@.age > 25)] - 选择满足条件的元素
- $..* - 递归选择所有元素`);
			}

			// 显示表达式预览
			function showPreview() {
				setTimeout(() => {
					expressionPreview.classList.remove('opacity-0', 'invisible');
				}, 200);
			}

			// 隐藏表达式预览
			function hidePreview() {
				expressionPreview.classList.add('opacity-0', 'invisible');
			}

			// 更新表达式预览
			function updatePreview() {
				try {
					if (!jsonInput.value.trim()) {
						previewResult.textContent = '请先输入JSON数据';
						return;
					}

					const jsonData = JSON.parse(jsonInput.value.trim());
					const expr = expression.value.trim() || '$';
					const result = evaluateJsonPath(jsonData, expr);

					if (result === undefined) {
						previewResult.textContent = '未找到匹配的数据';
					} else if (Array.isArray(result)) {
						previewResult.textContent = `找到数组 (${result.length} 项)`;
					} else if (typeof result === 'object') {
						previewResult.textContent = '找到对象';
					} else {
						previewResult.textContent = String(result);
					}
				} catch (error) {
					previewResult.textContent = `错误: ${error.message}`;
				}
			}

			// 显示加载指示器
			function showLoading() {
				loadingIndicator.classList.remove('hidden');
				resultTable.classList.add('hidden');
				noDataMessage.classList.add('hidden');
			}

			// 隐藏加载指示器
			function hideLoading() {
				loadingIndicator.classList.add('hidden');
			}

			// 显示通知
			function showNotification(message, type = 'info') {
				// 创建通知元素
				const notification = document.createElement('div');
				notification.className = `fixed top-4 right-4 px-4 py-3 rounded-lg shadow-lg transform transition-all duration-300 translate-x-full z-50 ${
        type === 'success' ? 'bg-success text-white' :
        type === 'error' ? 'bg-danger text-white' :
        type === 'warning' ? 'bg-warning text-white' : 'bg-info text-white'
      }`;

				notification.innerHTML = `
        <div class="flex items-center">
          <i class="fa ${
            type === 'success' ? 'fa-check-circle' :
            type === 'error' ? 'fa-exclamation-circle' :
            type === 'warning' ? 'fa-exclamation-triangle' : 'fa-info-circle'
          } mr-2"></i>
          <span>${message}</span>
        </div>
      `;

				// 添加到页面
				document.body.appendChild(notification);

				// 显示通知
				setTimeout(() => {
					notification.classList.remove('translate-x-full');
					notification.classList.add('translate-x-0');
				}, 10);

				// 自动关闭
				setTimeout(() => {
					notification.classList.remove('translate-x-0');
					notification.classList.add('translate-x-full');

					setTimeout(() => {
						document.body.removeChild(notification);
					}, 300);
				}, 3000);
			}

			// 格式化JSON字符串
			function formatJsonString(jsonString) {
				try {
					const parsed = JSON.parse(jsonString);
					return JSON.stringify(parsed, null, 2);
				} catch (error) {
					return jsonString;
				}
			}

			// 生成随机颜色
			function getRandomColor() {
				const letters = '0123456789ABCDEF';
				let color = '#';
				for (let i = 0; i < 6; i++) {
					color += letters[Math.floor(Math.random() * 16)];
				}
				return color;
			}
		</script>
	</body>
</html>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值