| 
									
										
										
										
											2025-02-05 14:18:02 +08:00
										 |  |  | <!DOCTYPE html> | 
					
						
							| 
									
										
										
										
											2025-02-27 17:36:27 +08:00
										 |  |  | <html lang="zh-Hans"> | 
					
						
							| 
									
										
										
										
											2025-02-05 14:18:02 +08:00
										 |  |  | <head> | 
					
						
							|  |  |  |     <meta charset="UTF-8"> | 
					
						
							| 
									
										
										
										
											2025-02-27 17:36:27 +08:00
										 |  |  |     <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> | 
					
						
							| 
									
										
										
										
											2025-02-05 14:18:02 +08:00
										 |  |  |     <style> | 
					
						
							| 
									
										
										
										
											2025-02-27 17:36:27 +08:00
										 |  |  |         :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; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         .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); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-02-27 18:01:16 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-05 14:18:02 +08:00
										 |  |  |     </style> | 
					
						
							|  |  |  | </head> | 
					
						
							|  |  |  | <body> | 
					
						
							| 
									
										
										
										
											2025-02-27 17:36:27 +08:00
										 |  |  | <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> | 
					
						
							| 
									
										
										
										
											2025-02-20 11:39:04 +08:00
										 |  |  | </div> | 
					
						
							| 
									
										
										
										
											2025-02-05 14:18:02 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | <script> | 
					
						
							| 
									
										
										
										
											2025-02-27 17:36:27 +08:00
										 |  |  |     const {createApp, ref, onMounted, computed} = Vue; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const API_CONFIG = { | 
					
						
							|  |  |  |         ENDPOINT: 'http://10.23.0.209: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"] | 
					
						
							| 
									
										
										
										
											2025-02-05 14:18:02 +08:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2025-02-05 15:57:40 +08:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-02-05 14:18:02 +08:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2025-02-27 17:36:27 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     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(() => { | 
					
						
							|  |  |  |                 return statusType.value ? `status-${statusType.value}` : ''; | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const setStatus = (message, type = 'info', timeout = 3000) => { | 
					
						
							|  |  |  |                 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'); | 
					
						
							| 
									
										
										
										
											2025-02-05 14:18:02 +08:00
										 |  |  | </script> | 
					
						
							|  |  |  | </body> | 
					
						
							|  |  |  | </html> |