来源 : 豆包
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解析与表格展示工具 © 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>