add json formatter

This commit is contained in:
2025-11-28 15:57:15 +08:00
parent 4ea625bc04
commit 45268544ea
2 changed files with 630 additions and 1 deletions

View File

@@ -382,6 +382,15 @@
<div class="tooltip">颜色选择器</div> <div class="tooltip">颜色选择器</div>
</div> </div>
<div class="tool-item" data-tool="json">
<div class="tool-icon">📋</div>
<div class="tool-info">
<h3>JSON 格式化</h3>
<p>JSON 代码格式化、压缩和验证工具</p>
</div>
<div class="tooltip">JSON 格式化</div>
</div>
<div class="tool-item" data-tool="other"> <div class="tool-item" data-tool="other">
<div class="tool-icon">⚙️</div> <div class="tool-icon">⚙️</div>
<div class="tool-info"> <div class="tool-info">
@@ -464,6 +473,10 @@
title: "颜色选择器", title: "颜色选择器",
url: "color_pick.html", url: "color_pick.html",
}, },
json: {
title: "JSON 格式化",
url: "json_formatter.html",
},
other: { other: {
title: "其他功能", title: "其他功能",
url: "other_function.html", url: "other_function.html",
@@ -536,12 +549,13 @@
// Ctrl/Cmd + 数字键快速切换工具 // Ctrl/Cmd + 数字键快速切换工具
if (e.ctrlKey || e.metaKey) { if (e.ctrlKey || e.metaKey) {
const key = parseInt(e.key); const key = parseInt(e.key);
if (key >= 1 && key <= 5) { if (key >= 1 && key <= 6) {
const toolNames = [ const toolNames = [
"welcome", "welcome",
"calculator", "calculator",
"number", "number",
"color", "color",
"json",
"other", "other",
]; ];
const toolName = toolNames[key - 1]; const toolName = toolNames[key - 1];

615
json_formatter.html Normal file
View File

@@ -0,0 +1,615 @@
<!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>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
margin: 0;
padding: 0;
background: white;
color: #333;
}
.container {
width: 100%;
height: 100vh;
display: flex;
flex-direction: column;
}
.header {
background: #f5f5f5;
padding: 20px;
text-align: center;
border-bottom: 1px solid #ddd;
}
.header h1 {
font-size: 1.5em;
margin-bottom: 5px;
font-weight: 600;
}
.header p {
font-size: 0.9em;
color: #666;
margin: 0;
}
.controls {
padding: 15px 20px;
background: #f9f9f9;
border-bottom: 1px solid #ddd;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 10px;
}
.control-group {
display: flex;
gap: 10px;
align-items: center;
}
button {
padding: 8px 16px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
display: flex;
align-items: center;
gap: 5px;
background: white;
}
button:hover {
background: #f0f0f0;
}
.btn-primary {
background: #007bff;
color: white;
border-color: #007bff;
}
.btn-primary:hover {
background: #0056b3;
}
.btn-secondary {
background: #6c757d;
color: white;
border-color: #6c757d;
}
.btn-secondary:hover {
background: #5a6268;
}
.btn-success {
background: #28a745;
color: white;
border-color: #28a745;
}
.btn-success:hover {
background: #218838;
}
.editor-container {
display: flex;
height: 600px;
}
.editor-panel {
flex: 1;
display: flex;
flex-direction: column;
position: relative;
}
.editor-header {
padding: 10px 15px;
background: #f5f5f5;
border-bottom: 1px solid #ddd;
font-weight: 600;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 14px;
}
.editor-content {
flex: 1;
position: relative;
}
textarea {
width: 100%;
height: 100%;
border: none;
outline: none;
padding: 15px;
font-family: "Consolas", "Monaco", "Courier New", monospace;
font-size: 13px;
line-height: 1.4;
resize: none;
background: white;
color: #333;
}
.output-area {
background: white;
padding: 15px;
font-family: "Consolas", "Monaco", "Courier New", monospace;
font-size: 13px;
line-height: 1.4;
white-space: pre-wrap;
overflow: auto;
height: 100%;
color: #333;
}
.error-message {
background: #f8d7da;
color: #721c24;
padding: 15px;
border-radius: 8px;
margin: 20px;
border: 1px solid #f5c6cb;
display: none;
}
.success-message {
background: #d4edda;
color: #155724;
padding: 15px;
border-radius: 8px;
margin: 20px;
border: 1px solid #c3e6cb;
display: none;
}
.divider {
width: 2px;
background: #ddd;
cursor: col-resize;
}
.divider:hover {
background: #bbb;
}
.status-bar {
background: #f5f5f5;
border-top: 1px solid #ddd;
padding: 8px 15px;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 12px;
color: #666;
}
.char-count {
color: #999;
}
.formatting-options {
display: flex;
gap: 15px;
align-items: center;
}
.option-group {
display: flex;
align-items: center;
gap: 5px;
}
.option-group label {
font-size: 14px;
color: #666;
}
.option-group input[type="number"] {
width: 60px;
padding: 4px 8px;
border: 1px solid #ddd;
border-radius: 3px;
font-size: 14px;
}
@media (max-width: 768px) {
.editor-container {
flex-direction: column;
height: auto;
}
.divider {
width: 100%;
height: 4px;
cursor: row-resize;
}
.divider::before {
content: "⋯";
}
.editor-panel {
min-height: 300px;
}
.controls {
flex-direction: column;
align-items: stretch;
}
.control-group {
justify-content: center;
}
}
.loading {
display: none;
color: #007bff;
font-style: italic;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>📋 JSON 格式化工具</h1>
<p>美化和验证您的 JSON 代码,支持实时格式化和错误检查</p>
</div>
<div class="controls">
<div class="control-group">
<button class="btn-primary" onclick="formatJSON()">格式化</button>
<button class="btn-secondary" onclick="minifyJSON()">压缩</button>
<button class="btn-success" onclick="copyFormatted()">
复制结果
</button>
<button class="btn-secondary" onclick="clearAll()">清空</button>
</div>
<div class="formatting-options">
<div class="option-group">
<label for="indentSize">缩进:</label>
<input
type="number"
id="indentSize"
value="2"
min="1"
max="8"
onchange="formatJSON()"
/>
</div>
<div class="option-group">
<input type="checkbox" id="sortKeys" onchange="formatJSON()" />
<label for="sortKeys">排序键名</label>
</div>
</div>
</div>
<div class="editor-container">
<div class="editor-panel">
<div class="editor-header">
<span>输入 JSON</span>
<span class="char-count" id="inputCount">0 字符</span>
</div>
<div class="editor-content">
<textarea
id="jsonInput"
placeholder='在此粘贴您的 JSON 代码...
例如: {"name": "张三", "age": 25}'
oninput="handleInputChange()"
></textarea>
</div>
</div>
<div class="divider"></div>
<div class="editor-panel">
<div class="editor-header">
<span>格式化结果</span>
<span class="char-count" id="outputCount">0 字符</span>
</div>
<div class="editor-content">
<div id="jsonOutput" class="output-area">
格式化结果将显示在这里...
</div>
</div>
</div>
</div>
<div class="error-message" id="errorMessage"></div>
<div class="success-message" id="successMessage"></div>
<div class="status-bar">
<span id="status">就绪</span>
<span id="formatStatus" class="loading">正在格式化...</span>
</div>
</div>
<script>
let isAutoFormatting = true;
function handleInputChange() {
updateCharCount();
if (isAutoFormatting) {
// 延迟格式化,避免频繁处理
clearTimeout(window.formatTimeout);
window.formatTimeout = setTimeout(() => {
formatJSON();
}, 500);
}
}
function updateCharCount() {
const input = document.getElementById("jsonInput");
const output = document.getElementById("jsonOutput");
const inputCount = document.getElementById("inputCount");
const outputCount = document.getElementById("outputCount");
inputCount.textContent = input.value.length + " 字符";
outputCount.textContent = output.textContent.length + " 字符";
}
function formatJSON() {
const input = document.getElementById("jsonInput").value.trim();
const output = document.getElementById("jsonOutput");
const errorMessage = document.getElementById("errorMessage");
const successMessage = document.getElementById("successMessage");
const formatStatus = document.getElementById("formatStatus");
const indentSize = parseInt(
document.getElementById("indentSize").value,
);
const sortKeys = document.getElementById("sortKeys").checked;
// 清除之前的消息
errorMessage.style.display = "none";
successMessage.style.display = "none";
formatStatus.style.display = "block";
if (!input) {
output.textContent = "格式化结果将显示在这里...";
formatStatus.style.display = "none";
updateCharCount();
return;
}
try {
// 解析 JSON
let jsonObj = JSON.parse(input);
// 如果选择排序键名
if (sortKeys && typeof jsonObj === "object" && jsonObj !== null) {
jsonObj = sortObjectKeys(jsonObj);
}
// 格式化 JSON
const formatted = JSON.stringify(jsonObj, null, indentSize);
// 语法高亮
output.innerHTML = highlightJSON(formatted);
// 显示成功消息
successMessage.textContent = "✅ JSON 格式化成功!";
successMessage.style.display = "block";
// 3秒后隐藏成功消息
setTimeout(() => {
successMessage.style.display = "none";
}, 3000);
} catch (error) {
// 显示错误
output.textContent = "";
errorMessage.innerHTML = `❌ JSON 格式错误: ${error.message}`;
errorMessage.style.display = "block";
}
formatStatus.style.display = "none";
updateCharCount();
}
function minifyJSON() {
const input = document.getElementById("jsonInput").value.trim();
const output = document.getElementById("jsonOutput");
const errorMessage = document.getElementById("errorMessage");
const successMessage = document.getElementById("successMessage");
if (!input) {
output.textContent = "请先输入 JSON 代码";
return;
}
try {
const jsonObj = JSON.parse(input);
const minified = JSON.stringify(jsonObj);
output.textContent = minified;
successMessage.textContent = "✅ JSON 压缩成功!";
successMessage.style.display = "block";
errorMessage.style.display = "none";
setTimeout(() => {
successMessage.style.display = "none";
}, 3000);
} catch (error) {
errorMessage.innerHTML = `❌ JSON 格式错误: ${error.message}`;
errorMessage.style.display = "block";
}
updateCharCount();
}
function sortObjectKeys(obj) {
if (Array.isArray(obj)) {
return obj.map(sortObjectKeys);
}
if (obj !== null && typeof obj === "object") {
const sorted = {};
Object.keys(obj)
.sort()
.forEach((key) => {
sorted[key] = sortObjectKeys(obj[key]);
});
return sorted;
}
return obj;
}
function highlightJSON(json) {
return json.replace(
/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g,
function (match) {
let cls = "number";
if (/^"/.test(match)) {
if (/:$/.test(match)) {
cls = "key";
} else {
cls = "string";
}
} else if (/true|false/.test(match)) {
cls = "boolean";
} else if (/null/.test(match)) {
cls = "null";
}
return (
'<span style="color: ' +
getJSONColor(cls) +
'">' +
match +
"</span>"
);
},
);
}
function getJSONColor(type) {
const colors = {
key: "#0969da",
string: "#032f62",
number: "#005cc5",
boolean: "#d73a49",
null: "#6f42c1",
};
return colors[type] || "#24292e";
}
function copyFormatted() {
const output = document.getElementById("jsonOutput");
const successMessage = document.getElementById("successMessage");
if (
!output.textContent ||
output.textContent === "格式化结果将显示在这里..."
) {
alert("没有可复制的内容!");
return;
}
// 获取纯文本内容
const textToCopy = output.textContent || output.innerText;
navigator.clipboard
.writeText(textToCopy)
.then(() => {
successMessage.textContent = "📋 已复制到剪贴板!";
successMessage.style.display = "block";
setTimeout(() => {
successMessage.style.display = "none";
}, 2000);
})
.catch(() => {
// 降级方案
const textArea = document.createElement("textarea");
textArea.value = textToCopy;
document.body.appendChild(textArea);
textArea.select();
document.execCommand("copy");
document.body.removeChild(textArea);
successMessage.textContent = "📋 已复制到剪贴板!";
successMessage.style.display = "block";
setTimeout(() => {
successMessage.style.display = "none";
}, 2000);
});
}
function clearAll() {
document.getElementById("jsonInput").value = "";
document.getElementById("jsonOutput").textContent =
"格式化结果将显示在这里...";
document.getElementById("errorMessage").style.display = "none";
document.getElementById("successMessage").style.display = "none";
updateCharCount();
}
// 键盘快捷键
document.addEventListener("keydown", function (e) {
// Ctrl/Cmd + Enter 格式化
if ((e.ctrlKey || e.metaKey) && e.key === "Enter") {
e.preventDefault();
formatJSON();
}
// Ctrl/Cmd + Shift + C 复制结果
if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === "C") {
e.preventDefault();
copyFormatted();
}
// Ctrl/Cmd + Shift + M 压缩
if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === "M") {
e.preventDefault();
minifyJSON();
}
});
// 页面加载完成后的初始化
document.addEventListener("DOMContentLoaded", function () {
const input = document.getElementById("jsonInput");
input.focus();
updateCharCount();
});
// 示例 JSON 数据
const exampleJSON = {
name: "张三",
age: 25,
city: "北京",
skills: ["JavaScript", "Python", "Java"],
profile: {
email: "zhangsan@example.com",
phone: "13800138000",
address: {
street: "朝阳区xxx街道",
zipcode: "100000",
},
},
isActive: true,
registeredAt: null,
};
// 如果需要加载示例,可以取消下面的注释
// document.getElementById('jsonInput').value = JSON.stringify(exampleJSON);
// formatJSON();
</script>
</body>
</html>