fdb2 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/.dockerignore +21 -0
  2. package/.editorconfig +11 -0
  3. package/.eslintrc.cjs +14 -0
  4. package/.eslintrc.json +7 -0
  5. package/.prettierrc.js +3 -0
  6. package/.tpl.env +22 -0
  7. package/README.md +260 -0
  8. package/bin/build.sh +28 -0
  9. package/bin/deploy.sh +8 -0
  10. package/bin/dev.sh +10 -0
  11. package/bin/docker/.env +4 -0
  12. package/bin/docker/dev-docker-compose.yml +43 -0
  13. package/bin/docker/dev.Dockerfile +24 -0
  14. package/bin/docker/prod-docker-compose.yml +17 -0
  15. package/bin/docker/prod.Dockerfile +29 -0
  16. package/bin/fdb2.js +142 -0
  17. package/data/connections.demo.json +32 -0
  18. package/env.d.ts +1 -0
  19. package/nw-build.js +120 -0
  20. package/nw-dev.js +65 -0
  21. package/package.json +114 -0
  22. package/public/favicon.ico +0 -0
  23. package/public/index.html +9 -0
  24. package/public/modules/header.tpl +14 -0
  25. package/public/modules/initial_state.tpl +55 -0
  26. package/server/index.ts +677 -0
  27. package/server/model/connection.entity.ts +66 -0
  28. package/server/model/database.entity.ts +246 -0
  29. package/server/service/connection.service.ts +334 -0
  30. package/server/service/database/base.service.ts +363 -0
  31. package/server/service/database/database.service.ts +510 -0
  32. package/server/service/database/index.ts +7 -0
  33. package/server/service/database/mssql.service.ts +723 -0
  34. package/server/service/database/mysql.service.ts +761 -0
  35. package/server/service/database/oracle.service.ts +839 -0
  36. package/server/service/database/postgres.service.ts +744 -0
  37. package/server/service/database/sqlite.service.ts +559 -0
  38. package/server/service/session.service.ts +158 -0
  39. package/server.js +128 -0
  40. package/src/adapter/ajax.ts +135 -0
  41. package/src/assets/base.css +1 -0
  42. package/src/assets/database.css +950 -0
  43. package/src/assets/images/collapse.png +0 -0
  44. package/src/assets/images/no-login.png +0 -0
  45. package/src/assets/images/svg/illustrations/illustration-1.svg +1 -0
  46. package/src/assets/images/svg/illustrations/illustration-2.svg +2 -0
  47. package/src/assets/images/svg/illustrations/illustration-3.svg +50 -0
  48. package/src/assets/images/svg/illustrations/illustration-4.svg +1 -0
  49. package/src/assets/images/svg/illustrations/illustration-5.svg +73 -0
  50. package/src/assets/images/svg/illustrations/illustration-6.svg +89 -0
  51. package/src/assets/images/svg/illustrations/illustration-7.svg +39 -0
  52. package/src/assets/images/svg/illustrations/illustration-8.svg +1 -0
  53. package/src/assets/images/svg/separators/curve-2.svg +3 -0
  54. package/src/assets/images/svg/separators/curve.svg +3 -0
  55. package/src/assets/images/svg/separators/line.svg +3 -0
  56. package/src/assets/images/theme/light/screen-1-1000x800.jpg +0 -0
  57. package/src/assets/images/theme/light/screen-2-1000x800.jpg +0 -0
  58. package/src/assets/login/bg.jpg +0 -0
  59. package/src/assets/login/bg.png +0 -0
  60. package/src/assets/login/left.jpg +0 -0
  61. package/src/assets/logo.svg +73 -0
  62. package/src/assets/logo.webp +0 -0
  63. package/src/assets/main.css +1 -0
  64. package/src/base/config.ts +20 -0
  65. package/src/base/detect.ts +134 -0
  66. package/src/base/entity.ts +92 -0
  67. package/src/base/eventBus.ts +37 -0
  68. package/src/base//345/237/272/347/241/200/345/261/202.md +7 -0
  69. package/src/components/connection-editor/index.vue +590 -0
  70. package/src/components/dataGrid/index.vue +105 -0
  71. package/src/components/dataGrid/pagination.vue +106 -0
  72. package/src/components/loading/index.vue +43 -0
  73. package/src/components/modal/index.ts +181 -0
  74. package/src/components/modal/index.vue +560 -0
  75. package/src/components/toast/index.ts +44 -0
  76. package/src/components/toast/toast.vue +58 -0
  77. package/src/components/user/name.vue +104 -0
  78. package/src/components/user/selector.vue +416 -0
  79. package/src/domain/SysConfig.ts +74 -0
  80. package/src/platform/App.vue +8 -0
  81. package/src/platform/database/components/connection-detail.vue +1154 -0
  82. package/src/platform/database/components/data-editor.vue +478 -0
  83. package/src/platform/database/components/data-import-export.vue +1602 -0
  84. package/src/platform/database/components/database-detail.vue +1173 -0
  85. package/src/platform/database/components/database-monitor.vue +1086 -0
  86. package/src/platform/database/components/db-tools.vue +577 -0
  87. package/src/platform/database/components/query-history.vue +1349 -0
  88. package/src/platform/database/components/sql-executor.vue +738 -0
  89. package/src/platform/database/components/sql-query-editor.vue +1046 -0
  90. package/src/platform/database/components/table-detail.vue +1376 -0
  91. package/src/platform/database/components/table-editor.vue +690 -0
  92. package/src/platform/database/explorer.vue +1840 -0
  93. package/src/platform/database/index.vue +1193 -0
  94. package/src/platform/database/layout.vue +367 -0
  95. package/src/platform/database/router.ts +37 -0
  96. package/src/platform/database/styles/common.scss +602 -0
  97. package/src/platform/database/types/common.ts +445 -0
  98. package/src/platform/database/utils/export.ts +232 -0
  99. package/src/platform/database/utils/helpers.ts +437 -0
  100. package/src/platform/index.ts +33 -0
  101. package/src/platform/router.ts +41 -0
  102. package/src/service/base.ts +128 -0
  103. package/src/service/database.ts +500 -0
  104. package/src/service/login.ts +121 -0
  105. package/src/shims-vue.d.ts +7 -0
  106. package/src/stores/connection.ts +266 -0
  107. package/src/stores/session.ts +87 -0
  108. package/src/typings/database-types.ts +413 -0
  109. package/src/typings/database.ts +364 -0
  110. package/src/typings/global.d.ts +58 -0
  111. package/src/typings/pinia.d.ts +8 -0
  112. package/src/utils/clipboard.ts +30 -0
  113. package/src/utils/database-types.ts +243 -0
  114. package/src/utils/modal.ts +124 -0
  115. package/src/utils/request.ts +55 -0
  116. package/src/utils/sleep.ts +4 -0
  117. package/src/utils/toast.ts +73 -0
  118. package/src/utils/util.ts +171 -0
  119. package/src/utils/xlsx.ts +228 -0
  120. package/tsconfig.json +33 -0
  121. package/tsconfig.server.json +19 -0
  122. package/view/index.html +9 -0
  123. package/view/modules/header.tpl +14 -0
  124. package/view/modules/initial_state.tpl +20 -0
  125. package/vite.config.ts +384 -0
@@ -0,0 +1,738 @@
1
+ <template>
2
+ <div class="sql-executor">
3
+ <div class="sql-toolbar">
4
+ <div class="toolbar-left">
5
+ <button class="btn btn-primary btn-sm" @click="executeSql" :disabled="loading">
6
+ <i class="bi bi-play-fill"></i> 执行SQL
7
+ </button>
8
+ <button class="btn btn-outline-secondary btn-sm" @click="formatSql">
9
+ <i class="bi bi-braces"></i> 格式化
10
+ </button>
11
+ </div>
12
+ <div class="toolbar-right">
13
+ <button class="btn btn-outline-primary btn-sm" @click="clearSql">
14
+ <i class="bi bi-trash"></i> 清空
15
+ </button>
16
+ </div>
17
+ </div>
18
+
19
+ <div class="sql-container" ref="containerRef">
20
+ <!-- SQL编辑器 -->
21
+ <div class="sql-editor">
22
+ <div ref="editorRef" class="codemirror-editor"></div>
23
+ </div>
24
+
25
+ <!-- 可拖动分隔栏 -->
26
+ <div
27
+ class="resizer"
28
+ @mousedown="startResize"
29
+ :class="{ 'resizing': isResizing }"
30
+ ></div>
31
+
32
+ <!-- SQL执行结果显示 -->
33
+ <div class="sql-result">
34
+ <div class="result-content">
35
+ <div class="result-header">
36
+ <h6 class="result-title">
37
+ <div v-if="loading" class="sql-loading">
38
+ <div class="spinner-border spinner-border-sm me-2"></div>
39
+ 执行中...
40
+ </div>
41
+ <template v-else-if="sqlResult">
42
+ <i class="bi bi-check-circle-fill text-success" v-if="sqlResult.success"></i>
43
+ <i class="bi bi-x-circle-fill text-danger" v-else></i>
44
+ 执行结果
45
+ </template>
46
+ <template v-else>
47
+ 执行结果
48
+ </template>
49
+ </h6>
50
+ <div class="result-actions" v-if="sqlResult && !loading">
51
+ <button class="btn btn-sm btn-outline-secondary me-2" @click="formatJsonResult">
52
+ <i class="bi bi-braces"></i> 格式化
53
+ </button>
54
+ <button class="btn btn-sm btn-outline-secondary" @click="exportResult('json')">
55
+ <i class="bi bi-file-earmark-code"></i> 导出JSON
56
+ </button>
57
+ </div>
58
+ </div>
59
+
60
+ <!-- 执行中的loading状态 -->
61
+ <div v-if="loading" class="sql-loading-state">
62
+ <div class="d-flex align-items-center justify-content-center py-4">
63
+ <div class="spinner-border text-primary me-3"></div>
64
+ <div>
65
+ <div class="fw-bold">正在执行SQL...</div>
66
+ <div class="text-muted small">请稍候,复杂查询可能需要较长时间</div>
67
+ </div>
68
+ </div>
69
+ </div>
70
+
71
+ <!-- JSON结果显示 -->
72
+ <div class="json-result">
73
+ <div ref="resultEditorRef" class="codemirror-editor"></div>
74
+ </div>
75
+ </div>
76
+ </div>
77
+ </div>
78
+ </div>
79
+ </template>
80
+
81
+ <script lang="ts" setup>
82
+ import { ref, computed, onMounted, watch } from 'vue';
83
+ // 导入CodeMirror相关模块
84
+ import { EditorState } from '@codemirror/state';
85
+ import { EditorView, keymap, lineNumbers, highlightActiveLineGutter, highlightActiveLine, drawSelection, placeholder } from '@codemirror/view';
86
+ import { defaultKeymap } from '@codemirror/commands';
87
+ import { sql } from '@codemirror/lang-sql';
88
+ import { json } from '@codemirror/lang-json';
89
+ import { oneDark } from '@codemirror/theme-one-dark';
90
+ import { defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language';
91
+
92
+ // 导入其他依赖
93
+ import { DatabaseService } from '@/service/database';
94
+ import { modal } from '@/utils/modal';
95
+ import { exportDataToCSV, exportDataToJSON, formatFileName } from '../utils/export';
96
+
97
+ // Props
98
+ const props = defineProps<{
99
+ connection: any;
100
+ database: string;
101
+ height?: number;
102
+ }>();
103
+
104
+ const databaseService = new DatabaseService();
105
+
106
+ // 响应式数据
107
+ const sqlQuery = ref('');
108
+ const loading = ref(false);
109
+ const sqlResult = ref<any>(null);
110
+ const containerRef = ref<HTMLElement | null>(null);
111
+ const editorRef = ref<HTMLElement | null>(null);
112
+ const resultEditorRef = ref<HTMLElement | null>(null);
113
+ const editor = ref<EditorView | null>(null);
114
+ const resultEditor = ref<EditorView | null>(null);
115
+ const isResizing = ref(false);
116
+ const height = ref(props.height || 0);
117
+ const editorHeight = ref(0);
118
+ const resultHeight = computed(() => height.value - editorHeight.value - 8);
119
+
120
+ // 方法
121
+ function startResize(event: MouseEvent) {
122
+ isResizing.value = true;
123
+ const startY = event.clientY;
124
+ const container = containerRef.value;
125
+ if (!container) return;
126
+
127
+ const containerHeight = container.clientHeight;
128
+ const startEditorHeight = container.querySelector('.sql-editor')?.clientHeight || 0;
129
+
130
+ function onMouseMove(e: MouseEvent) {
131
+ if (!isResizing.value || !container) return;
132
+ const deltaY = e.clientY - startY;
133
+ const newEditorHeight = startEditorHeight + deltaY;
134
+ const newEditorRatio = newEditorHeight / containerHeight;
135
+
136
+ if (newEditorRatio > 0.2 && newEditorRatio < 0.8) {
137
+ const sqlEditor = container.querySelector('.sql-editor');
138
+ if (sqlEditor) {
139
+ sqlEditor.style.flex = `0 0 ${newEditorRatio * 100}%`;
140
+ }
141
+ }
142
+ }
143
+
144
+ function onMouseUp() {
145
+ isResizing.value = false;
146
+ document.removeEventListener('mousemove', onMouseMove);
147
+ document.removeEventListener('mouseup', onMouseUp);
148
+ }
149
+
150
+ document.addEventListener('mousemove', onMouseMove);
151
+ document.addEventListener('mouseup', onMouseUp);
152
+ }
153
+
154
+ async function executeSql() {
155
+ if (!sqlQuery.value.trim()) {
156
+ modal.warning('请输入SQL语句');
157
+ return;
158
+ }
159
+
160
+ if (!props.connection) {
161
+ modal.error('请先选择数据库连接');
162
+ return;
163
+ }
164
+
165
+ loading.value = true;
166
+ sqlResult.value = null;
167
+
168
+ try {
169
+ const result = await databaseService.executeQuery(
170
+ props.connection.id,
171
+ sqlQuery.value,
172
+ props.database
173
+ );
174
+
175
+ if (typeof result.data === 'object' && result.data !== null) {
176
+ // 判断执行是否成功:ret === 0 表示成功,否则表示失败
177
+ const isSuccess = result.ret === 0;
178
+
179
+ sqlResult.value = {
180
+ success: isSuccess,
181
+ data: isSuccess ? (result.data || []) : null,
182
+ error: !isSuccess ? result.msg : null
183
+ };
184
+ }
185
+ // 处理其他情况
186
+ else {
187
+ sqlResult.value = {
188
+ success: true,
189
+ data: result,
190
+ error: null
191
+ };
192
+ }
193
+ } catch (error: any) {
194
+ sqlResult.value = {
195
+ success: false,
196
+ error: error.message || '执行SQL时发生未知错误'
197
+ };
198
+ } finally {
199
+ loading.value = false;
200
+ // 更新结果编辑器内容
201
+ if (!loading.value && sqlResult.value) {
202
+ updateResultEditor();
203
+ }
204
+ }
205
+ }
206
+
207
+ function formatSql() {
208
+ // 简单的SQL格式化
209
+ let formatted = sqlQuery.value
210
+ .replace(/\bSELECT\b/gi, '\nSELECT ')
211
+ .replace(/\bFROM\b/gi, '\nFROM ')
212
+ .replace(/\bWHERE\b/gi, '\nWHERE ')
213
+ .replace(/\bJOIN\b/gi, '\nJOIN ')
214
+ .replace(/\bAND\b/gi, '\n AND ')
215
+ .replace(/\bOR\b/gi, '\n OR ')
216
+ .replace(/\bGROUP BY\b/gi, '\nGROUP BY ')
217
+ .replace(/\bORDER BY\b/gi, '\nORDER BY ');
218
+
219
+ sqlQuery.value = formatted.trim();
220
+ // 更新编辑器内容
221
+ if (editor.value) {
222
+ editor.value.dispatch({
223
+ changes: {
224
+ from: 0,
225
+ to: editor.value.state.doc.length,
226
+ insert: sqlQuery.value
227
+ }
228
+ });
229
+ }
230
+ }
231
+
232
+ function clearSql() {
233
+ sqlQuery.value = '';
234
+ sqlResult.value = null;
235
+ // 清空编辑器内容
236
+ if (editor.value) {
237
+ editor.value.dispatch({
238
+ changes: {
239
+ from: 0,
240
+ to: editor.value.state.doc.length,
241
+ insert: ''
242
+ }
243
+ });
244
+ }
245
+ }
246
+
247
+ function formatCellValue(value: any): string {
248
+ if (value === null || value === undefined) return 'NULL';
249
+
250
+ // 尝试检测并格式化 JSON 数据
251
+ let strValue = String(value);
252
+ if (typeof value === 'string') {
253
+ const trimmedValue = strValue.trim();
254
+ if ((trimmedValue.startsWith('{') && trimmedValue.endsWith('}')) ||
255
+ (trimmedValue.startsWith('[') && trimmedValue.endsWith(']'))) {
256
+ try {
257
+ const parsed = JSON.parse(trimmedValue);
258
+ const formatted = JSON.stringify(parsed, null, 2);
259
+ if (formatted.length > 50) {
260
+ return formatted.substring(0, 50) + '...';
261
+ }
262
+ return formatted;
263
+ } catch (e) {
264
+ // 不是有效的 JSON,继续处理
265
+ }
266
+ }
267
+ } else if (typeof value === 'object') {
268
+ try {
269
+ const formatted = JSON.stringify(value, null, 2);
270
+ if (formatted.length > 50) {
271
+ return formatted.substring(0, 50) + '...';
272
+ }
273
+ return formatted;
274
+ } catch (e) {
275
+ // 格式化失败,继续处理
276
+ }
277
+ }
278
+
279
+ // 对于普通字符串,限制显示长度
280
+ if (strValue.length > 50) return strValue.substring(0, 50) + '...';
281
+
282
+ return strValue;
283
+ }
284
+
285
+ function exportResult(format: 'json') {
286
+ if (!sqlResult.value) {
287
+ return;
288
+ }
289
+
290
+ const filename = formatFileName('sql_result', format);
291
+
292
+ // 准备要导出的JSON数据
293
+ let jsonData;
294
+ if (sqlResult.value.success) {
295
+ jsonData = sqlResult.value.data;
296
+ } else {
297
+ jsonData = {
298
+ error: sqlResult.value.error
299
+ };
300
+ }
301
+
302
+ // 导出JSON数据
303
+ exportDataToJSON(jsonData, filename);
304
+ }
305
+
306
+ // 初始化SQL编辑器
307
+ function initEditor() {
308
+ if (!editorRef.value) return;
309
+
310
+ // 创建编辑器状态
311
+ const state = EditorState.create({
312
+ doc: sqlQuery.value,
313
+ extensions: [
314
+ lineNumbers(),
315
+ highlightActiveLineGutter(),
316
+ highlightActiveLine(),
317
+ drawSelection(),
318
+ placeholder('输入SQL查询语句...'),
319
+ sql(),
320
+ oneDark,
321
+ keymap.of(defaultKeymap),
322
+ EditorView.updateListener.of(update => {
323
+ if (update.docChanged) {
324
+ sqlQuery.value = update.state.doc.toString();
325
+ }
326
+ }),
327
+ EditorView.lineWrapping,
328
+ EditorView.theme({
329
+ '&': {
330
+ height: '100%',
331
+ fontSize: '14px',
332
+ fontFamily: 'Monaco, Menlo, Consolas, "Courier New", monospace'
333
+ },
334
+ '.cm-content': {
335
+ padding: '10px',
336
+ minHeight: '100%'
337
+ },
338
+ '.cm-gutters': {
339
+ backgroundColor: '#282c34',
340
+ color: '#abb2bf',
341
+ border: 'none'
342
+ },
343
+ '.cm-activeLineGutter': {
344
+ backgroundColor: '#282c34'
345
+ },
346
+ '.cm-activeLine': {
347
+ backgroundColor: 'rgba(255, 255, 255, 0.1)'
348
+ }
349
+ })
350
+ ]
351
+ });
352
+
353
+ // 创建编辑器视图
354
+ editor.value = new EditorView({
355
+ state,
356
+ parent: editorRef.value
357
+ });
358
+ }
359
+
360
+ // 初始化结果编辑器
361
+ function initResultEditor() {
362
+ if (!resultEditorRef.value) return;
363
+
364
+ // 创建编辑器状态
365
+ const state = EditorState.create({
366
+ doc: '',
367
+ extensions: [
368
+ lineNumbers(),
369
+ highlightActiveLineGutter(),
370
+ highlightActiveLine(),
371
+ drawSelection(),
372
+ placeholder('执行SQL以查看结果...'),
373
+ json(),
374
+ syntaxHighlighting(defaultHighlightStyle),
375
+ keymap.of(defaultKeymap),
376
+ EditorView.lineWrapping,
377
+ // 设置只读
378
+ EditorState.readOnly.of(true),
379
+ EditorView.theme({
380
+ '&': {
381
+ height: '100%',
382
+ fontSize: '14px',
383
+ fontFamily: 'Monaco, Menlo, Consolas, "Courier New", monospace',
384
+ backgroundColor: '#f8f9fa',
385
+ color: '#212529'
386
+ },
387
+ '.cm-content': {
388
+ padding: '10px',
389
+ minHeight: '100%',
390
+ backgroundColor: '#ffffff',
391
+ color: '#212529',
392
+ border: '1px solid #dee2e6'
393
+ },
394
+ '.cm-gutters': {
395
+ backgroundColor: '#f8f9fa',
396
+ color: '#6c757d',
397
+ border: '1px solid #dee2e6',
398
+ borderRight: 'none'
399
+ },
400
+ '.cm-activeLineGutter': {
401
+ backgroundColor: '#e9ecef'
402
+ },
403
+ '.cm-activeLine': {
404
+ backgroundColor: '#e9ecef'
405
+ },
406
+ '.cm-selectionBackground': {
407
+ backgroundColor: '#cce7ff'
408
+ },
409
+ '.cm-line': {
410
+ color: '#212529'
411
+ }
412
+ })
413
+ ]
414
+ });
415
+
416
+ // 创建编辑器视图
417
+ resultEditor.value = new EditorView({
418
+ state,
419
+ parent: resultEditorRef.value
420
+ });
421
+ }
422
+
423
+ // 更新结果编辑器内容
424
+ function updateResultEditor() {
425
+ if (!resultEditor.value || !sqlResult.value) return;
426
+
427
+ // 准备要显示的JSON数据
428
+ let jsonData;
429
+ if (sqlResult.value.success) {
430
+ jsonData = sqlResult.value.data || sqlResult.value;
431
+ } else {
432
+ jsonData = {
433
+ error: sqlResult.value.error
434
+ };
435
+ }
436
+
437
+ // 格式化JSON字符串
438
+ try {
439
+ const jsonString = JSON.stringify(jsonData, null, 2);
440
+
441
+ // 更新编辑器内容
442
+ resultEditor.value.dispatch({
443
+ changes: {
444
+ from: 0,
445
+ to: resultEditor.value.state.doc.length,
446
+ insert: jsonString
447
+ }
448
+ });
449
+ } catch (error) {
450
+ // 如果JSON.stringify失败,直接显示原始数据
451
+ resultEditor.value.dispatch({
452
+ changes: {
453
+ from: 0,
454
+ to: resultEditor.value.state.doc.length,
455
+ insert: String(jsonData)
456
+ }
457
+ });
458
+ }
459
+ }
460
+
461
+ // 格式化JSON结果
462
+ function formatJsonResult() {
463
+ if (!resultEditor.value || !sqlResult.value) return;
464
+
465
+ // 准备要显示的JSON数据
466
+ let jsonData;
467
+ if (sqlResult.value.success) {
468
+ jsonData = sqlResult.value.data || sqlResult.value;
469
+ } else {
470
+ jsonData = {
471
+ error: sqlResult.value.error
472
+ };
473
+ }
474
+
475
+ // 格式化JSON字符串
476
+ try {
477
+ const jsonString = JSON.stringify(jsonData, null, 2);
478
+
479
+ // 更新编辑器内容
480
+ resultEditor.value.dispatch({
481
+ changes: {
482
+ from: 0,
483
+ to: resultEditor.value.state.doc.length,
484
+ insert: jsonString
485
+ }
486
+ });
487
+ } catch (error) {
488
+ // 如果JSON.stringify失败,显示错误信息
489
+ resultEditor.value.dispatch({
490
+ changes: {
491
+ from: 0,
492
+ to: resultEditor.value.state.doc.length,
493
+ insert: '无法格式化结果: ' + String(error)
494
+ }
495
+ });
496
+ }
497
+ }
498
+
499
+ // 生命周期
500
+ onMounted(() => {
501
+ // 初始化编辑器
502
+ initEditor();
503
+ initResultEditor();
504
+ });
505
+
506
+
507
+
508
+ // 监听 sqlResult 变化,更新结果编辑器内容
509
+ watch(sqlResult, () => {
510
+ // 当 sqlResult 变化且 resultEditor 已初始化时,更新结果编辑器内容
511
+ if (sqlResult.value && resultEditor.value) {
512
+ updateResultEditor();
513
+ }
514
+ });
515
+ </script>
516
+
517
+ <style scoped>
518
+ .sql-executor {
519
+ width: 100%;
520
+ flex: 1;
521
+ display: flex;
522
+ flex-direction: column;
523
+ gap: 10px;
524
+ }
525
+
526
+ .sql-toolbar {
527
+ display: flex;
528
+ justify-content: space-between;
529
+ align-items: center;
530
+ margin-bottom: 10px;
531
+ padding: 10px;
532
+ background-color: #f8f9fa;
533
+ border-radius: 4px;
534
+ }
535
+
536
+ .toolbar-left,
537
+ .toolbar-right {
538
+ display: flex;
539
+ gap: 8px;
540
+ }
541
+
542
+ .sql-container {
543
+ position: relative;
544
+ border: 1px solid #dee2e6;
545
+ border-radius: 4px;
546
+ overflow: hidden;
547
+ flex: 1;
548
+ display: flex;
549
+ flex-direction: column;
550
+ }
551
+
552
+ .sql-editor {
553
+ position: relative;
554
+ overflow: auto;
555
+ flex: 0 1 300px;
556
+ min-height: 200px;
557
+ }
558
+
559
+ .sql-result {
560
+ position: relative;
561
+ overflow: auto;
562
+ border-top: 1px solid #dee2e6;
563
+ flex: 1;
564
+ }
565
+
566
+ .codemirror-editor {
567
+ height: 100%;
568
+ width: 100%;
569
+ }
570
+
571
+ .resizer {
572
+ height: 8px;
573
+ background-color: #e9ecef;
574
+ cursor: ns-resize;
575
+ display: flex;
576
+ align-items: center;
577
+ justify-content: center;
578
+ }
579
+
580
+ .resizer:hover {
581
+ background-color: #dee2e6;
582
+ }
583
+
584
+ .resizer::before {
585
+ content: '';
586
+ width: 40px;
587
+ height: 2px;
588
+ background-color: #adb5bd;
589
+ border-radius: 1px;
590
+ }
591
+
592
+ .resizer.resizing {
593
+ background-color: #dee2e6;
594
+ }
595
+
596
+ .resizer.resizing::before {
597
+ background-color: #6c757d;
598
+ }
599
+
600
+ .sql-result {
601
+ position: relative;
602
+ overflow: auto;
603
+ border-top: 1px solid #dee2e6;
604
+ }
605
+
606
+ .result-content {
607
+ height: 100%;
608
+ display: flex;
609
+ flex-direction: column;
610
+ }
611
+
612
+ .result-header {
613
+ display: flex;
614
+ justify-content: space-between;
615
+ align-items: center;
616
+ padding: 12px;
617
+ background-color: #f8f9fa;
618
+ border-bottom: 1px solid #dee2e6;
619
+ }
620
+
621
+ .result-title {
622
+ margin: 0;
623
+ font-size: 14px;
624
+ font-weight: 600;
625
+ display: flex;
626
+ align-items: center;
627
+ gap: 8px;
628
+ }
629
+
630
+ .sql-loading {
631
+ display: flex;
632
+ align-items: center;
633
+ gap: 8px;
634
+ }
635
+
636
+ .result-stats {
637
+ display: flex;
638
+ gap: 8px;
639
+ }
640
+
641
+ .result-info {
642
+ display: flex;
643
+ justify-content: space-between;
644
+ align-items: center;
645
+ padding: 10px 12px;
646
+ background-color: #f8f9fa;
647
+ border-bottom: 1px solid #dee2e6;
648
+ }
649
+
650
+ .result-actions {
651
+ display: flex;
652
+ gap: 8px;
653
+ }
654
+
655
+ .result-table-container {
656
+ flex: 1;
657
+ overflow: auto;
658
+ }
659
+
660
+ /* 对象结果样式 */
661
+ .result-object {
662
+ display: flex;
663
+ flex-direction: column;
664
+ height: 100%;
665
+ }
666
+
667
+ .object-container {
668
+ flex: 1;
669
+ overflow: auto;
670
+ }
671
+
672
+ .object-container table {
673
+ width: 100%;
674
+ }
675
+
676
+ .object-container th:first-child {
677
+ width: 20%;
678
+ min-width: 100px;
679
+ }
680
+
681
+ .object-container td:first-child {
682
+ font-weight: 500;
683
+ background-color: #f8f9fa;
684
+ }
685
+
686
+ .sql-loading-state {
687
+ display: flex;
688
+ align-items: center;
689
+ justify-content: center;
690
+ height: 100%;
691
+ flex: 1;
692
+ }
693
+
694
+ .sql-empty-result,
695
+ .sql-error {
696
+ padding: 12px;
697
+ flex: 1;
698
+ }
699
+
700
+ .result-empty {
701
+ display: flex;
702
+ flex-direction: column;
703
+ align-items: center;
704
+ justify-content: center;
705
+ height: 100%;
706
+ color: #6c757d;
707
+ gap: 10px;
708
+ }
709
+
710
+ .result-empty i {
711
+ font-size: 48px;
712
+ opacity: 0.5;
713
+ }
714
+
715
+ .json-result {
716
+ flex: 1;
717
+ overflow: hidden;
718
+ }
719
+
720
+ .json-result .codemirror-editor {
721
+ height: 100%;
722
+ width: 100%;
723
+ }
724
+
725
+ /* 响应式设计 */
726
+ @media (max-width: 768px) {
727
+ .sql-toolbar {
728
+ flex-direction: column;
729
+ align-items: stretch;
730
+ gap: 8px;
731
+ }
732
+
733
+ .toolbar-left,
734
+ .toolbar-right {
735
+ justify-content: center;
736
+ }
737
+ }
738
+ </style>