<!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>