<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Code Formatter | 高级代码格式化工具</title>
<style>
:root {
--primary: #2563eb;
--primary-light: #3b82f6;
--primary-dark: #1d4ed8;
--dark: #1e293b;
--light: #f8fafc;
--gray: #94a3b8;
--gray-light: #e2e8f0;
--success: #10b981;
--error: #ef4444;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
line-height: 1.6;
background-color: #f1f5f9;
color: var(--dark);
min-height: 100vh;
padding: 0;
}
.app-container {
max-width: 1200px;
margin: 0 auto;
padding: 1rem;
display: flex;
flex-direction: column;
min-height: 100vh;
}
header {
margin-bottom: 1.5rem;
text-align: center;
padding: 0 0.5rem;
}
h1 {
font-size: 1.5rem;
font-weight: 600;
color: var(--dark);
margin-bottom: 0.25rem;
letter-spacing: -0.025em;
}
.subtitle {
color: var(--gray);
font-size: 0.875rem;
font-weight: 400;
}
.main-content {
display: flex;
flex-direction: column;
flex: 1;
background: white;
border-radius: 0.75rem;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
overflow: hidden;
}
.toolbar {
display: flex;
flex-direction: column;
gap: 0.75rem;
padding: 0.75rem;
border-bottom: 1px solid var(--gray-light);
background: white;
}
.toolbar-row {
display: flex;
gap: 0.75rem;
align-items: center;
}
.mode-selector {
display: flex;
gap: 0.25rem;
overflow-x: auto;
padding-bottom: 0.25rem;
scrollbar-width: none;
flex: 1;
}
.mode-selector::-webkit-scrollbar {
display: none;
}
.mode-btn {
padding: 0.5rem 0.75rem;
font-size: 0.8125rem;
font-weight: 500;
border-radius: 0.375rem;
cursor: pointer;
border: 1px solid var(--gray-light);
background: white;
color: var(--dark);
transition: all 0.2s ease;
white-space: nowrap;
}
.mode-btn:hover {
background: #f8fafc;
}
.mode-btn.active {
background: var(--primary);
color: white;
border-color: var(--primary);
}
.settings {
display: flex;
gap: 0.5rem;
align-items: center;
}
.indent-control {
display: flex;
align-items: center;
gap: 0.25rem;
}
.indent-control label {
font-size: 0.8125rem;
color: var(--dark);
white-space: nowrap;
}
.indent-control input {
width: 3rem;
padding: 0.25rem 0.5rem;
border: 1px solid var(--gray-light);
border-radius: 0.25rem;
text-align: center;
}
.action-buttons {
display: flex;
gap: 0.5rem;
justify-content: space-between;
}
.btn {
padding: 0.5rem 0.75rem;
font-size: 0.8125rem;
font-weight: 500;
border-radius: 0.375rem;
cursor: pointer;
border: none;
transition: all 0.2s ease;
display: flex;
align-items: center;
gap: 0.375rem;
flex: 1;
justify-content: center;
}
.btn-primary {
background: var(--primary);
color: white;
}
.btn-primary:hover {
background: var(--primary-light);
}
.btn-secondary {
background: white;
color: var(--dark);
border: 1px solid var(--gray-light);
}
.btn-secondary:hover {
background: #f8fafc;
}
.btn-success {
background: var(--success);
color: white;
}
.btn-success:hover {
background: #0d9f6e;
}
.code-editor {
display: flex;
flex-direction: column;
flex: 1;
min-height: 400px;
position: relative;
}
.input-section, .output-section {
flex: 1;
display: flex;
flex-direction: column;
padding: 1rem;
}
.input-section {
border-bottom: 1px solid var(--gray-light);
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.75rem;
}
.section-title {
font-size: 0.8125rem;
font-weight: 600;
color: var(--dark);
text-transform: uppercase;
letter-spacing: 0.05em;
}
textarea, .output-content {
flex: 1;
width: 100%;
padding: 0.75rem;
font-family: 'Fira Code', 'Consolas', monospace;
font-size: 0.8125rem;
line-height: 1.7;
border: 1px solid var(--gray-light);
border-radius: 0.5rem;
resize: none;
background: #f8fafc;
color: var(--dark);
min-height: 150px;
}
textarea:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
}
.output-content {
overflow: auto;
white-space: pre-wrap;
background: #f8fafc;
}
.status-bar {
padding: 0.75rem;
border-top: 1px solid var(--gray-light);
background: white;
display: flex;
flex-direction: column;
gap: 0.5rem;
font-size: 0.8125rem;
}
.status-info {
display: flex;
align-items: center;
gap: 0.5rem;
justify-content: space-between;
}
.status-message {
color: var(--dark);
font-size: 0.8125rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.status-message.success {
color: var(--success);
}
.status-message.error {
color: var(--error);
}
.detected-type {
background: var(--gray-light);
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
font-size: 0.75rem;
font-weight: 500;
white-space: nowrap;
}
@media (min-width: 768px) {
.app-container {
padding: 2rem;
}
.toolbar {
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
padding: 1rem 1.5rem;
gap: 1rem;
}
.toolbar-row {
flex: 1;
gap: 1rem;
}
.mode-selector {
gap: 0.5rem;
}
.mode-btn {
padding: 0.5rem 1rem;
font-size: 0.875rem;
}
.action-buttons {
gap: 0.75rem;
justify-content: flex-end;
}
.btn {
padding: 0.5rem 1rem;
font-size: 0.875rem;
flex: none;
}
.code-editor {
flex-direction: row;
min-height: 500px;
}
.input-section {
border-bottom: none;
border-right: 1px solid var(--gray-light);
}
.input-section, .output-section {
padding: 1.5rem;
}
.status-bar {
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 1rem 1.5rem;
}
h1 {
font-size: 2rem;
margin-bottom: 0.5rem;
}
.subtitle {
font-size: 1rem;
}
.settings {
gap: 1rem;
}
}
</style>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Fira+Code&display=swap" rel="stylesheet">
</head>
<body>
<div class="app-container">
<header>
<h1>代码格式化工具</h1>
<p class="subtitle">高级简约的代码格式化解决方案</p>
</header>
<div class="main-content">
<div class="toolbar">
<div class="toolbar-row">
<div class="mode-selector">
<div class="mode-btn active" data-mode="auto">自动</div>
<div class="mode-btn" data-mode="sql">SQL</div>
<div class="mode-btn" data-mode="json">JSON</div>
<div class="mode-btn" data-mode="html">HTML</div>
<div class="mode-btn" data-mode="css">CSS</div>
<div class="mode-btn" data-mode="js">JS</div>
</div>
<div class="settings">
<div class="indent-control">
<label for="indent-size">缩进:</label>
<input type="number" id="indent-size" min="1" max="8" value="4">
<span>空格</span>
</div>
</div>
</div>
<div class="action-buttons">
<button class="btn btn-primary" onclick="formatCode()">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="4 17 10 11 4 5"></polyline>
<line x1="12" y1="19" x2="20" y2="19"></line>
</svg>
格式化
</button>
<button class="btn btn-secondary" onclick="minifyCode()">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="5 12 12 5 19 12"></polyline>
<polyline points="12 19 12 5"></polyline>
</svg>
压缩
</button>
</div>
</div>
<div class="code-editor">
<div class="input-section">
<div class="section-header">
<div class="section-title">输入代码</div>
<button class="btn btn-secondary" onclick="clearCode()">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="3 6 5 6 21 6"></polyline>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
</svg>
清空
</button>
</div>
<textarea id="code-input" placeholder="在此粘贴您的代码..."></textarea>
</div>
<div class="output-section">
<div class="section-header">
<div class="section-title">格式化结果</div>
<button class="btn btn-success" onclick="copyToClipboard()">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
</svg>
复制
</button>
</div>
<div id="code-output" class="output-content"></div>
</div>
</div>
<div class="status-bar">
<div class="status-info">
<span id="status-message" class="status-message">准备就绪</span>
<span id="detected-type" class="detected-type" style="display: none;"></span>
</div>
<button class="btn btn-secondary" onclick="autoDetect()">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<path d="M12 8v4l3 3"></path>
</svg>
重新检测
</button>
</div>
</div>
</div>
<script>
let currentMode = 'auto';
let detectedType = null;
let indentSize = 4;
// 初始化模式选择
document.querySelectorAll('.mode-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
currentMode = btn.dataset.mode;
updateStatus();
});
});
// 监听缩进设置变化
document.getElementById('indent-size').addEventListener('change', function() {
indentSize = parseInt(this.value) || 4;
if (indentSize < 1) indentSize = 1;
if (indentSize > 8) indentSize = 8;
this.value = indentSize;
});
// 更新状态显示
function updateStatus() {
const statusElement = document.getElementById('status-message');
const typeElement = document.getElementById('detected-type');
if (currentMode === 'auto') {
statusElement.textContent = '自动模式';
if (detectedType) {
typeElement.textContent = `检测到: ${detectedType.toUpperCase()}`;
typeElement.style.display = 'block';
} else {
typeElement.style.display = 'none';
}
} else {
statusElement.textContent = `手动模式: ${currentMode.toUpperCase()}`;
typeElement.style.display = 'none';
}
statusElement.className = 'status-message';
}
// 复制到剪贴板
function copyToClipboard() {
const element = document.getElementById('code-output');
const range = document.createRange();
range.selectNode(element);
window.getSelection().removeAllRanges();
window.getSelection().addRange(range);
try {
const successful = document.execCommand('copy');
if (successful) {
showStatus('已复制到剪贴板', 'success');
} else {
showStatus('复制失败', 'error');
}
} catch (err) {
showStatus('复制失败', 'error');
}
window.getSelection().removeAllRanges();
}
// 自动检测代码类型
function autoDetectCode(input) {
if (!input.trim()) {
detectedType = null;
return null;
}
// 尝试检测JSON
try {
JSON.parse(input);
detectedType = 'json';
return 'json';
} catch (e) {}
// 检测HTML
if (/<[a-z][\s\S]*>/i.test(input)) {
detectedType = 'html';
return 'html';
}
// 检测SQL
const sqlKeywords = ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'CREATE', 'ALTER', 'DROP', 'FROM', 'WHERE', 'JOIN'];
const sqlScore = sqlKeywords.reduce((score, keyword) => {
return score + (new RegExp(`\\b${keyword}\\b`, 'gi').test(input) ? 1 : 0);
}, 0);
if (sqlScore >= 2) {
detectedType = 'sql';
return 'sql';
}
// 检测CSS
const cssPatterns = [/\{[^{}]*\}/, /:[^:;]*;/, /\.\w+\s*\{/, /#\w+\s*\{/];
const cssScore = cssPatterns.reduce((score, pattern) => {
return score + (pattern.test(input) ? 1 : 0);
}, 0);
if (cssScore >= 2) {
detectedType = 'css';
return 'css';
}
// 检测JavaScript
const jsPatterns = [/function\s*\(/, /=\s*\{/, /\)\s*\{/, /;\s*$/, /\/\/|\/\*/];
const jsScore = jsPatterns.reduce((score, pattern) => {
return score + (pattern.test(input) ? 1 : 0);
}, 0);
if (jsScore >= 2) {
detectedType = 'js';
return 'js';
}
detectedType = null;
return null;
}
// 自动检测按钮
function autoDetect() {
const input = document.getElementById('code-input').value;
autoDetectCode(input);
updateStatus();
if (detectedType) {
showStatus(`已检测为 ${detectedType.toUpperCase()}`, 'success');
} else {
showStatus('无法识别代码类型', 'error');
}
}
// 生成缩进字符串
function getIndent() {
return ' '.repeat(indentSize);
}
// 格式化代码
function formatCode() {
const input = document.getElementById('code-input').value;
if (!input.trim()) {
showStatus('请输入代码', 'error');
return;
}
const type = currentMode === 'auto' ? autoDetectCode(input) : currentMode;
if (!type && currentMode === 'auto') {
showStatus('无法识别代码格式,请手动选择', 'error');
return;
}
try {
let formatted;
switch (type) {
case 'sql':
formatted = formatSql(input);
break;
case 'json':
formatted = formatJson(input);
break;
case 'html':
formatted = formatHtml(input);
break;
case 'css':
formatted = formatCss(input);
break;
case 'js':
formatted = formatJs(input);
break;
default:
showStatus('不支持的类型', 'error');
return;
}
document.getElementById('code-output').textContent = formatted;
showStatus(`格式化成功 (${type.toUpperCase()})`, 'success');
} catch (e) {
document.getElementById('code-output').textContent = `格式化错误: ${e.message}`;
showStatus('格式化失败', 'error');
}
}
// 压缩代码
function minifyCode() {
const input = document.getElementById('code-input').value;
if (!input.trim()) {
showStatus('请输入代码', 'error');
return;
}
const type = currentMode === 'auto' ? autoDetectCode(input) : currentMode;
if (!type && currentMode === 'auto') {
showStatus('无法识别代码格式,请手动选择', 'error');
return;
}
try {
let minified;
switch (type) {
case 'sql':
minified = minifySql(input);
break;
case 'json':
minified = minifyJson(input);
break;
case 'html':
minified = minifyHtml(input);
break;
case 'css':
minified = minifyCss(input);
break;
case 'js':
minified = minifyJs(input);
break;
default:
showStatus('不支持的类型', 'error');
return;
}
document.getElementById('code-output').textContent = minified;
showStatus(`压缩成功 (${type.toUpperCase()})`, 'success');
} catch (e) {
document.getElementById('code-output').textContent = `压缩错误: ${e.message}`;
showStatus('压缩失败', 'error');
}
}
// 清空代码
function clearCode() {
document.getElementById('code-input').value = '';
document.getElementById('code-output').textContent = '';
detectedType = null;
updateStatus();
showStatus('已清空', 'success');
}
// 显示状态信息
function showStatus(message, type = 'success') {
const statusElement = document.getElementById('status-message');
statusElement.textContent = message;
statusElement.className = `status-message ${type}`;
}
// ========== 格式化函数 ==========
// SQL 格式化
function formatSql(sql) {
// 简单的SQL格式化逻辑
let formatted = sql
.replace(/\b(SELECT|FROM|WHERE|GROUP BY|HAVING|ORDER BY|INSERT INTO|UPDATE|DELETE FROM|CREATE TABLE|ALTER TABLE|DROP TABLE|JOIN|LEFT JOIN|RIGHT JOIN|INNER JOIN|OUTER JOIN|UNION|VALUES|SET)\b/gi, '\n$1')
.replace(/,/g, ',\n' + getIndent())
.replace(/\)\s*\(/g, ')\n(')
.replace(/\b(AND|OR)\b/gi, '\n' + getIndent() + '$1');
// 添加缩进
let indent = 0;
formatted = formatted.split('\n').map(line => {
const trimmed = line.trim();
if (trimmed.match(/^\)/) || trimmed.match(/^(END|ELSE|ELSIF|WHEN)\b/i)) {
indent--;
}
const indentedLine = getIndent().repeat(Math.max(0, indent)) + trimmed;
if (trimmed.match(/\(|\b(BEGIN|CASE|IF|ELSE|ELSIF|WHEN)\b/i)) {
indent++;
}
return indentedLine;
}).join('\n');
return formatted.trim();
}
function minifySql(sql) {
// 简单的SQL压缩逻辑
return sql
.replace(/\/\*.*?\*\//gs, '') // 删除注释
.replace(/--.*$/gm, '') // 删除行注释
.replace(/\s+/g, ' ') // 多个空格变一个
.replace(/\s*([,;()=+\-*\/])\s*/g, '$1') // 删除运算符周围的空格
.trim();
}
// JSON 格式化
function formatJson(json) {
const obj = JSON.parse(json);
return JSON.stringify(obj, null, indentSize);
}
function minifyJson(json) {
const obj = JSON.parse(json);
return JSON.stringify(obj);
}
// HTML 格式化
function formatHtml(html) {
// 添加换行
let formatted = html
.replace(/></g, '>\n<')
.replace(/\n\n/g, '\n');
// 添加缩进
let indent = 0;
let inTag = false;
let inComment = false;
formatted = formatted.split('\n').map(line => {
const trimmed = line.trim();
// 跳过空行
if (!trimmed) return '';
// 处理注释
if (trimmed.startsWith('<!--')) {
inComment = true;
}
if (inComment && trimmed.endsWith('-->')) {
inComment = false;
return getIndent().repeat(indent) + trimmed;
}
if (inComment) {
return getIndent().repeat(indent) + trimmed;
}
// 处理结束标签
if (trimmed.startsWith('</')) {
indent = Math.max(0, indent - 1);
}
const indentedLine = getIndent().repeat(indent) + trimmed;
// 处理开始标签(非自闭合)
if (trimmed.startsWith('<') && !trimmed.endsWith('/>') &&
!trimmed.match(/<(meta|link|img|br|hr|input|area|base|col|command|embed|keygen|param|source|track|wbr)[^>]*>/i)) {
if (!trimmed.match(/<\/[^>]+>$/)) {
indent++;
}
}
return indentedLine;
}).filter(line => line !== '').join('\n');
return formatted;
}
function minifyHtml(html) {
return html
.replace(/<!--.*?-->/gs, '') // 删除注释
.replace(/\s+/g, ' ') // 多个空格变一个
.replace(/>\s+</g, '><') // 删除标签间的空格
.trim();
}
// CSS 格式化
function formatCss(css) {
// 添加基本格式化
let formatted = css
.replace(/\{/g, ' {\n' + getIndent())
.replace(/\}/g, '\n}\n\n')
.replace(/;/g, ';\n' + getIndent())
.replace(/,\s*/g, ',\n' + getIndent());
// 处理媒体查询
formatted = formatted.replace(/@media[^{]+\{/g, match => {
return match.replace(/\s+/g, ' ') + ' {\n';
});
return formatted.trim();
}
function minifyCss(css) {
return css
.replace(/\/\*.*?\*\//gs, '') // 删除注释
.replace(/\s+/g, ' ') // 多个空格变一个
.replace(/\s*([{:;,])\s*/g, '$1') // 删除符号周围的空格
.replace(/;}/g, '}') // 删除最后一个分号
.trim();
}
// JavaScript 格式化
function formatJs(js) {
// 使用简单缩进格式化
let formatted = '';
let indent = 0;
let inString = false;
let stringChar = '';
let inComment = false;
let inLineComment = false;
for (let i = 0; i < js.length; i++) {
const char = js[i];
const nextChar = js[i+1];
// 处理注释
if (!inString && !inComment && char === '/' && nextChar === '*') {
inComment = true;
formatted += '/*';
i++;
continue;
}
if (inComment && char === '*' && nextChar === '/') {
inComment = false;
formatted += '*/';
i++;
continue;
}
if (!inString && !inComment && !inLineComment && char === '/' && nextChar === '/') {
inLineComment = true;
formatted += '//';
i++;
continue;
}
if (inLineComment && char === '\n') {
inLineComment = false;
}
if (inComment || inLineComment) {
formatted += char;
continue;
}
// 处理字符串
if (inString) {
formatted += char;
if (char === stringChar && js[i-1] !== '\\') {
inString = false;
}
continue;
} else if (char === '"' || char === "'" || char === '`') {
inString = true;
stringChar = char;
formatted += char;
continue;
}
// 处理大括号和缩进
if (char === '{' || char === '[') {
formatted += char + '\n' + getIndent().repeat(++indent);
} else if (char === '}' || char === ']') {
formatted += '\n' + getIndent().repeat(--indent) + char;
} else if (char === ';') {
formatted += ';\n' + getIndent().repeat(indent);
} else if (char === ',') {
formatted += ',\n' + getIndent().repeat(indent);
} else if (char === ':') {
formatted += ': ';
} else {
formatted += char;
}
}
return formatted.trim();
}
function minifyJs(js) {
// 简单的JS压缩逻辑
return js
.replace(/\/\/.*$/gm, '') // 删除单行注释
.replace(/\/\*.*?\*\//gs, '') // 删除多行注释
.replace(/\s+/g, ' ') // 多个空格变一个
.replace(/\s*([=+\-*\/%&|^~!<>?:;,{}()[\]])\s*/g, '$1') // 删除运算符周围的空格
.replace(/;}/g, '}') // 删除最后一个分号
.trim();
}
// 初始化
updateStatus();
// 监听输入变化自动检测
document.getElementById('code-input').addEventListener('input', function() {
if (currentMode === 'auto') {
autoDetectCode(this.value);
updateStatus();
}
});
</script>
</body>
</html>
index.html