2025-02-27 19:05:15 +08:00

319 lines
10 KiB
HTML

<!DOCTYPE html>
<html lang="zh-Hans">
<head>
<meta charset="UTF-8">
<title>SQL 处理器</title>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/codemirror.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/theme/monokai.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/codemirror.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/sql/sql.min.js"></script>
<style>
:root {
--primary-color: #4a90e2;
--hover-color: #2c78c8;
--bg-color: #f0f2f5;
--border-color: #ddd;
--success-color: #28a745;
--error-color: #dc3545;
--warning-color: #ffc107;
}
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: var(--bg-color);
}
h1 {
font-size: 2.5rem;
color: var(--primary-color);
margin: 0 0 20px;
font-weight: 500;
}
.container {
margin-bottom: 20px;
background: white;
padding: 15px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.CodeMirror {
height: 300px;
border: 1px solid var(--border-color);
border-radius: 6px;
resize: vertical;
}
.buttons {
margin: 15px 0;
display: flex;
gap: 10px;
flex-wrap: wrap;
}
button {
background: var(--primary-color);
color: #fff;
border: none;
border-radius: 6px;
padding: 0.75rem 1.5rem;
font: 600 1rem 'Roboto', sans-serif;
cursor: pointer;
transition: background 0.3s ease;
}
button:hover {
background: var(--hover-color);
}
button:disabled {
background: #cccccc;
cursor: not-allowed;
}
.status-message {
font-size: 0.9em;
margin-top: 10px;
min-height: 1.2em;
padding: 8px;
border-radius: 4px;
/*设置高度*/
overflow: hidden;
overflow-y: auto;
word-wrap: break-word;
overflow-wrap: break-word;
word-break: break-all;
/*设置宽度*/
max-width: 100%;
}
.status-success {
background: #e8f5e9;
color: var(--success-color);
}
.status-error {
background: #ffebee;
color: var(--error-color);
}
.status-warning {
background: #fff3e0;
color: var(--warning-color);
}
.status-info {
background: #e3f2fd;
color: var(--primary-color);
}
</style>
</head>
<body>
<div id="app">
<!-- 基本结构:图片作为超链接内容 -->
<a href="/">
<img src="https://yinzhou.oss-cn-shanghai.aliyuncs.com/image/bianmu.png" width="50" height="50">
</a>
<a href="/a">
<img src="https://yinzhou.oss-cn-shanghai.aliyuncs.com/image/buoumao.png" width="50" height="50">
</a>
<a href="/b">
<img src="https://yinzhou.oss-cn-shanghai.aliyuncs.com/image/chaiquan.png" width="50" height="50">
</a>
<header><h1>SQL格式化工具</h1></header>
<div class="container">
<h2>输入 SQL</h2>
<div ref="sqlInput"></div>
</div>
<div class="buttons">
<button @click="insertSample" title="插入示例语句">示例</button>
<button @click="clearInput" title="清空输入框">清空</button>
<button @click="formatSQL" :disabled="isProcessing">格式化</button>
<button @click="copyToClipboard">复制</button>
</div>
<div class="status-message" :class="statusClass">{{ statusMessage }}</div>
<div class="container">
<h2>输出 SQL</h2>
<div ref="sqlOutput"></div>
</div>
</div>
<script>
const {createApp, ref, onMounted, computed} = Vue;
const API_CONFIG = {
ENDPOINT: 'http://localhost:8778/convert',
DEFAULT_HEADERS: {
'Content-Type': 'application/x-www-form-urlencoded'
},
DEFAULT_SQL: "SELECT * FROM table WHERE condition;",
SAMPLE_SQL: "SELECT * FROM users WHERE age > 18;"
};
const createEditorConfig = (readOnly = false) => ({
mode: "sql",
lineNumbers: true,
theme: "monokai",
readOnly,
extraKeys: {"Ctrl-Space": "autocomplete"},
hintOptions: {
completeSingle: false,
tables: {
users: ["id", "name", "age"],
products: ["id", "name", "price"]
}
}
});
createApp({
setup() {
const sqlInput = ref(null);
const sqlOutput = ref(null);
const isProcessing = ref(false);
const statusMessage = ref('');
const statusType = ref('');
let editorInput = null;
let editorOutput = null;
const outputContent = computed(() => {
return editorOutput ? editorOutput.getValue().trim() : '';
});
const statusClass = computed(() => {
// 控制台打印
console.log(`1Status: ${statusType.value} - ${statusMessage.value}`);
return statusType.value ? `status-${statusType.value}` : '';
});
const setStatus = (message, type = 'info', timeout = 10000) => {
statusMessage.value = message;
statusType.value = type;
if (timeout) {
setTimeout(() => {
statusMessage.value = '';
statusType.value = '';
}, timeout);
}
};
onMounted(() => {
if (sqlInput.value && sqlOutput.value) {
editorInput = CodeMirror(sqlInput.value, {
...createEditorConfig(),
value: API_CONFIG.DEFAULT_SQL
});
editorOutput = CodeMirror(sqlOutput.value, {
...createEditorConfig(true),
value: "默认输出内容" // 给输出框一个默认值
});
}
});
const insertSample = () => {
editorInput.setValue(API_CONFIG.SAMPLE_SQL);
setStatus('示例语句已插入', 'success');
};
const clearInput = () => {
editorInput.setValue('');
setStatus('输入已清空', 'info');
};
const formatSQL = async () => {
const inputSQL = editorInput.getValue().trim();
if (!inputSQL) {
setStatus('请输入要格式化的SQL内容', 'warning');
return;
}
try {
isProcessing.value = true;
setStatus('正在格式化SQL...', 'info', 0);
const startTime = Date.now();
const response = await fetch(API_CONFIG.ENDPOINT, {
method: 'POST',
headers: API_CONFIG.DEFAULT_HEADERS,
body: `sql=${encodeURIComponent(inputSQL)}`
});
const duration = Date.now() - startTime;
if (!response.ok) {
throw new Error(`请求失败: ${response.status} ${response.statusText}`);
}
const data = await response.json();
if (!data.parsed_result) {
throw new Error('服务器返回空结果');
}
editorOutput.setValue(data.parsed_result);
setStatus(`格式化成功 (${duration}ms)`, 'success');
} catch (error) {
console.error('格式化错误:', error);
editorOutput.setValue('');
setStatus(`格式化失败: ${error.message}`, 'error');
} finally {
isProcessing.value = false;
}
};
const copyToClipboard = async () => {
try {
const content = editorOutput.getValue().trim();
if (!content) {
setStatus('无内容可复制', 'warning');
return;
}
try {
await navigator.clipboard.writeText(content);
setStatus('内容已复制到剪贴板', 'success');
} catch (err) {
// 回退到传统复制方式
const textarea = document.createElement('textarea');
textarea.value = content;
document.body.appendChild(textarea);
textarea.select();
const success = document.execCommand('copy');
document.body.removeChild(textarea);
if (!success) throw new Error('传统复制方式失败');
setStatus('内容已复制(传统方式)', 'success');
}
} catch (error) {
console.error('复制失败:', error);
setStatus(`复制失败: ${error.message}`, 'error');
}
};
return {
sqlInput,
sqlOutput,
isProcessing,
statusMessage,
statusClass,
outputContent,
insertSample,
clearInput,
formatSQL,
copyToClipboard
};
}
}).mount('#app');
</script>
</body>
</html>