Files
python_script/flask-dev-api/templates/json_format.html
2026-06-16 03:30:57 +08:00

224 lines
10 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{{ url_for('static', filename='font-awesome.css') }}">
<title>JSON 美化 / 压缩</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background: #f5f5f5;
color: #333;
height: 100vh;
overflow: hidden;
display: flex;
}
.left-panel {
width: 300px;
background: #fff;
border-right: 1px solid #e0e0e0;
display: flex;
flex-direction: column;
}
.panel-header {
padding: 28px 24px 18px;
border-bottom: 1px solid #f0f0f0;
}
.panel-header h1 { font-size: 20px; font-weight: 700; margin-bottom: 2px; }
.panel-header .sub { font-size: 13px; color: #999; }
.panel-body { padding: 20px 24px; flex: 1; overflow-y: auto; }
.group { margin-bottom: 24px; }
.group-title { font-size: 11px; font-weight: 600; color: #aaa; text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 10px; }
.tabs { display: flex; background: #f5f5f5; border-radius: 8px; padding: 3px; gap: 3px; }
.tab-btn {
flex: 1; padding: 9px 0; text-align: center;
font-size: 13px; font-weight: 600; color: #999;
background: none; border: none; border-radius: 6px; cursor: pointer; transition: all 0.2s;
}
.tab-btn.on { background: #fff; color: #0078d4; box-shadow: 0 1px 3px rgba(0,0,0,0.08); }
.tab-btn:hover:not(.on) { color: #666; }
select.styled {
width: 100%; padding: 9px 32px 9px 12px;
border: 1px solid #e0e0e0; border-radius: 8px;
font-size: 13px; color: #333; background: #fff;
outline: none; cursor: pointer;
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 10 10'%3E%3Cpath fill='%23999' d='M5 7L1 3h8z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 10px center;
transition: border-color 0.2s;
}
select.styled:focus { border-color: #0078d4; }
.chk-label { display: flex; align-items: center; gap: 8px; cursor: pointer; font-size: 13px; color: #555; }
.chk-label input { display: none; }
.chk-icon {
width: 16px; height: 16px; border: 2px solid #d0d0d0; border-radius: 4px;
display: flex; align-items: center; justify-content: center; transition: all 0.15s;
}
.chk-icon i { font-size: 10px; color: #fff; opacity: 0; transition: opacity 0.15s; }
.chk-label input:checked ~ .chk-icon { background: #0078d4; border-color: #0078d4; }
.chk-label input:checked ~ .chk-icon i { opacity: 1; }
.panel-actions { padding: 16px 24px; border-top: 1px solid #f0f0f0; }
.panel-actions button {
width: 100%; padding: 10px; border: none; border-radius: 8px;
font-size: 13px; font-weight: 600; cursor: pointer;
display: flex; align-items: center; justify-content: center; gap: 8px;
margin-bottom: 8px; transition: all 0.15s;
}
.panel-actions button:last-child { margin-bottom: 0; }
.btn-main { background: #0078d4; color: #fff; }
.btn-main:hover { background: #006cbd; }
.btn-main.copied { background: #2e7d32; }
.btn-ghost { background: #fff; color: #666; border: 1px solid #e0e0e0; }
.btn-ghost:hover { background: #f5f5f5; color: #333; }
.btn-ghost.danger:hover { color: #c62828; border-color: #f5b3b3; background: #fff5f5; }
.right { flex: 1; display: flex; }
.pane { flex: 1; display: flex; flex-direction: column; background: #fff; }
.pane:first-child { border-right: 1px solid #e0e0e0; }
.pane-head {
padding: 12px 20px; font-size: 11px; font-weight: 600; color: #aaa;
text-transform: uppercase; letter-spacing: 0.06em;
border-bottom: 1px solid #f0f0f0;
display: flex; align-items: center; gap: 6px;
}
.pane-head i { font-size: 11px; color: #ccc; }
.pane-body { flex: 1; display: flex; flex-direction: column; }
.pane-body textarea {
flex: 1; width: 100%; border: none; outline: none; resize: none;
padding: 16px 20px;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
font-size: 13px; line-height: 1.7; color: #333; background: #fff;
}
.pane-body textarea::placeholder { color: #ccc; }
.pane-body textarea:focus { background: #fefefe; }
.pane-body textarea.has-error { background: #fff8f8; }
.err-bar {
padding: 8px 20px; font-size: 12px; color: #c62828;
background: #fff5f5; border-top: 1px solid #fde8e8;
display: none; align-items: center; gap: 6px;
}
.err-bar.on { display: flex; }
</style>
</head>
<body>
<div class="left-panel">
<div class="panel-header">
<h1>JSON 工具</h1>
<span class="sub">美化 &amp; 压缩</span>
</div>
<div class="panel-body">
<div class="group">
<div class="group-title">模式</div>
<div class="tabs">
<button class="tab-btn on" data-tab="beautify" onclick="switchTab('beautify')">美化</button>
<button class="tab-btn" data-tab="minify" onclick="switchTab('minify')">压缩</button>
</div>
</div>
<div id="beautifyOpts">
<div class="group">
<div class="group-title">缩进空格</div>
<select id="indentSize" class="styled" onchange="process()">
<option value="2">2</option>
<option value="4" selected>4</option>
<option value="6">6</option>
<option value="8">8</option>
</select>
</div>
<div class="group">
<label class="chk-label">
<input type="checkbox" id="sortKeys" onchange="process()">
<span class="chk-icon"><i class="fas fa-check"></i></span>
<span>键名排序</span>
</label>
</div>
</div>
</div>
<div class="panel-actions">
<button class="btn-main" id="copyBtn" onclick="copyOutput()"><i class="fas fa-copy"></i><span>复制结果</span></button>
<button class="btn-ghost" onclick="swapIO()"><i class="fas fa-refresh"></i> 交换输入输出</button>
<button class="btn-ghost danger" onclick="clearAll()"><i class="fas fa-trash"></i> 清空</button>
</div>
</div>
<div class="right">
<div class="pane">
<div class="pane-head"><i class="fas fa-file"></i> 输入</div>
<div class="pane-body">
<textarea id="input" placeholder="在此粘贴 JSON..." oninput="process()"></textarea>
<div class="err-bar" id="errBar"><i class="fas fa-times-circle"></i><span id="errMsg"></span></div>
</div>
</div>
<div class="pane">
<div class="pane-head"><i class="fas fa-check"></i> <span id="outLabel">美化结果</span></div>
<div class="pane-body">
<textarea id="output" readonly placeholder="结果将显示在这里..."></textarea>
</div>
</div>
</div>
<script>
var cur = 'beautify';
function switchTab(t) {
cur = t;
document.querySelectorAll('.tab-btn').forEach(function(b) { b.classList.toggle('on', b.dataset.tab === t); });
document.getElementById('beautifyOpts').style.display = t === 'beautify' ? 'block' : 'none';
document.getElementById('outLabel').textContent = t === 'beautify' ? '美化结果' : '压缩结果';
process();
}
function sortKeys(o) {
if (typeof o !== 'object' || o === null) return o;
if (Array.isArray(o)) return o.map(sortKeys);
return Object.keys(o).sort().reduce(function(a, k) { a[k] = sortKeys(o[k]); return a; }, {});
}
function process() {
var v = document.getElementById('input').value;
var errBar = document.getElementById('errBar');
var errMsg = document.getElementById('errMsg');
var out = document.getElementById('output');
var inp = document.getElementById('input');
if (!v.trim()) { out.value = ''; errBar.classList.remove('on'); inp.classList.remove('has-error'); return; }
try {
var obj = JSON.parse(v);
if (cur === 'beautify') {
var n = parseInt(document.getElementById('indentSize').value);
out.value = JSON.stringify(document.getElementById('sortKeys').checked ? sortKeys(obj) : obj, null, n);
} else {
out.value = JSON.stringify(obj);
}
errBar.classList.remove('on'); inp.classList.remove('has-error');
} catch (e) {
errMsg.textContent = e.message; errBar.classList.add('on'); inp.classList.add('has-error'); out.value = '';
}
}
function copyOutput() {
var t = document.getElementById('output').value;
if (!t) return;
var el = document.createElement('textarea');
el.value = t; el.style.cssText = 'position:fixed;left:-9999px';
document.body.appendChild(el); el.select(); document.execCommand('copy'); document.body.removeChild(el);
var b = document.getElementById('copyBtn');
b.classList.add('copied'); b.querySelector('span').textContent = '已复制';
setTimeout(function() { b.classList.remove('copied'); b.querySelector('span').textContent = '复制结果'; }, 1500);
}
function swapIO() {
var a = document.getElementById('input'), b = document.getElementById('output'), t = a.value;
a.value = b.value; b.value = t; process();
}
function clearAll() {
document.getElementById('input').value = '';
document.getElementById('output').value = '';
document.getElementById('errBar').classList.remove('on');
document.getElementById('input').classList.remove('has-error');
}
</script>
</body>
</html>