fdb2 1.0.8 → 1.0.9

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 (235) hide show
  1. package/.dockerignore +21 -21
  2. package/.editorconfig +11 -11
  3. package/.eslintrc.cjs +14 -14
  4. package/.eslintrc.json +7 -7
  5. package/.prettierrc.js +3 -3
  6. package/.tpl.env +21 -21
  7. package/.vscodeignore +45 -45
  8. package/README.md +312 -312
  9. package/bin/build.sh +28 -28
  10. package/bin/deploy.sh +8 -8
  11. package/bin/dev.sh +10 -10
  12. package/bin/docker/dev-docker-compose.yml +43 -43
  13. package/bin/docker/dev.Dockerfile +24 -24
  14. package/bin/docker/prod-docker-compose.yml +17 -17
  15. package/bin/docker/prod.Dockerfile +29 -29
  16. package/bin/fdb2.js +220 -220
  17. package/dist/package.json +29 -29
  18. package/dist/pnpm-lock.yaml +1042 -354
  19. package/dist/public/explorer.css +1464 -1437
  20. package/dist/public/explorer.js +759 -223
  21. package/dist/public/index.css +1026 -1026
  22. package/dist/public/index.js +15 -9
  23. package/dist/public/layout.css +221 -221
  24. package/dist/public/layout.js +1 -1
  25. package/dist/public/vue.js +8 -2
  26. package/dist/scripts/preinstall.js +112 -112
  27. package/dist/server/index.d.ts.map +1 -1
  28. package/dist/server/index.js +8 -0
  29. package/dist/server/index.js.map +1 -1
  30. package/dist/server/index.ts +680 -671
  31. package/dist/server/model/connection.entity.ts +65 -65
  32. package/dist/server/model/database.entity.ts +245 -245
  33. package/dist/server/service/connection.service.d.ts +6 -1
  34. package/dist/server/service/connection.service.d.ts.map +1 -1
  35. package/dist/server/service/connection.service.js +15 -0
  36. package/dist/server/service/connection.service.js.map +1 -1
  37. package/dist/server/service/connection.service.ts +356 -341
  38. package/dist/server/service/database/base.service.d.ts +27 -0
  39. package/dist/server/service/database/base.service.d.ts.map +1 -1
  40. package/dist/server/service/database/base.service.js +17 -0
  41. package/dist/server/service/database/base.service.js.map +1 -1
  42. package/dist/server/service/database/base.service.ts +406 -367
  43. package/dist/server/service/database/cockroachdb.service.d.ts +16 -0
  44. package/dist/server/service/database/cockroachdb.service.d.ts.map +1 -1
  45. package/dist/server/service/database/cockroachdb.service.js +220 -154
  46. package/dist/server/service/database/cockroachdb.service.js.map +1 -1
  47. package/dist/server/service/database/cockroachdb.service.ts +871 -782
  48. package/dist/server/service/database/database.service.d.ts +4 -0
  49. package/dist/server/service/database/database.service.d.ts.map +1 -1
  50. package/dist/server/service/database/database.service.js +123 -0
  51. package/dist/server/service/database/database.service.js.map +1 -1
  52. package/dist/server/service/database/database.service.ts +775 -638
  53. package/dist/server/service/database/index.ts +6 -6
  54. package/dist/server/service/database/mongodb.service.d.ts +16 -0
  55. package/dist/server/service/database/mongodb.service.d.ts.map +1 -1
  56. package/dist/server/service/database/mongodb.service.js +35 -0
  57. package/dist/server/service/database/mongodb.service.js.map +1 -1
  58. package/dist/server/service/database/mongodb.service.ts +39 -1
  59. package/dist/server/service/database/mssql.service.d.ts +16 -0
  60. package/dist/server/service/database/mssql.service.d.ts.map +1 -1
  61. package/dist/server/service/database/mssql.service.js +168 -96
  62. package/dist/server/service/database/mssql.service.js.map +1 -1
  63. package/dist/server/service/database/mssql.service.ts +931 -840
  64. package/dist/server/service/database/mysql.service.d.ts +16 -0
  65. package/dist/server/service/database/mysql.service.d.ts.map +1 -1
  66. package/dist/server/service/database/mysql.service.js +189 -80
  67. package/dist/server/service/database/mysql.service.js.map +1 -1
  68. package/dist/server/service/database/mysql.service.ts +1025 -890
  69. package/dist/server/service/database/oracle.service.d.ts +16 -0
  70. package/dist/server/service/database/oracle.service.d.ts.map +1 -1
  71. package/dist/server/service/database/oracle.service.js +182 -120
  72. package/dist/server/service/database/oracle.service.js.map +1 -1
  73. package/dist/server/service/database/oracle.service.ts +1035 -959
  74. package/dist/server/service/database/postgres.service.d.ts +16 -0
  75. package/dist/server/service/database/postgres.service.d.ts.map +1 -1
  76. package/dist/server/service/database/postgres.service.js +154 -88
  77. package/dist/server/service/database/postgres.service.js.map +1 -1
  78. package/dist/server/service/database/postgres.service.ts +960 -871
  79. package/dist/server/service/database/sap.service.d.ts +16 -0
  80. package/dist/server/service/database/sap.service.d.ts.map +1 -1
  81. package/dist/server/service/database/sap.service.js +66 -0
  82. package/dist/server/service/database/sap.service.js.map +1 -1
  83. package/dist/server/service/database/sap.service.ts +89 -0
  84. package/dist/server/service/database/sqlite.service.d.ts +16 -0
  85. package/dist/server/service/database/sqlite.service.d.ts.map +1 -1
  86. package/dist/server/service/database/sqlite.service.js +77 -18
  87. package/dist/server/service/database/sqlite.service.js.map +1 -1
  88. package/dist/server/service/database/sqlite.service.ts +787 -708
  89. package/dist/server/service/session.service.ts +158 -158
  90. package/dist/view/index.html +38 -38
  91. package/env.d.ts +1 -1
  92. package/package.json +1 -1
  93. package/packages/vscode/.vscodeignore +44 -44
  94. package/packages/vscode/README.md +62 -62
  95. package/packages/vscode/out/database-services/cockroachdb.service.js +154 -154
  96. package/packages/vscode/out/database-services/mssql.service.js +96 -96
  97. package/packages/vscode/out/database-services/mysql.service.js +80 -80
  98. package/packages/vscode/out/database-services/oracle.service.js +120 -120
  99. package/packages/vscode/out/database-services/postgres.service.js +88 -88
  100. package/packages/vscode/out/database-services/sqlite.service.js +18 -18
  101. package/packages/vscode/out/provider/WebViewProvider.js +32 -32
  102. package/packages/vscode/package.json +142 -142
  103. package/packages/vscode/resources/icon.svg +5 -5
  104. package/packages/vscode/resources/webview/connection.css +41 -41
  105. package/packages/vscode/resources/webview/database.css +163 -163
  106. package/packages/vscode/resources/webview/index.html +9 -9
  107. package/packages/vscode/resources/webview/modules/header.tpl +13 -13
  108. package/packages/vscode/resources/webview/modules/initial_state.tpl +54 -54
  109. package/packages/vscode/resources/webview/query.css +104 -104
  110. package/packages/vscode/src/database-services/base.service.ts +362 -362
  111. package/packages/vscode/src/database-services/cockroachdb.service.ts +659 -659
  112. package/packages/vscode/src/database-services/connection.service.ts +340 -340
  113. package/packages/vscode/src/database-services/database.service.ts +629 -629
  114. package/packages/vscode/src/database-services/index.ts +6 -6
  115. package/packages/vscode/src/database-services/model/connection.entity.ts +65 -65
  116. package/packages/vscode/src/database-services/model/database.entity.ts +245 -245
  117. package/packages/vscode/src/database-services/mssql.service.ts +722 -722
  118. package/packages/vscode/src/database-services/mysql.service.ts +760 -760
  119. package/packages/vscode/src/database-services/oracle.service.ts +831 -831
  120. package/packages/vscode/src/database-services/postgres.service.ts +740 -740
  121. package/packages/vscode/src/database-services/sqlite.service.ts +558 -558
  122. package/packages/vscode/src/extension.ts +76 -76
  123. package/packages/vscode/src/provider/DatabaseTreeProvider.ts +167 -167
  124. package/packages/vscode/src/provider/WebViewProvider.ts +277 -277
  125. package/packages/vscode/src/service/DatabaseServiceBridge.ts +414 -414
  126. package/packages/vscode/src/typings/connection.ts +90 -90
  127. package/packages/vscode/tsconfig.json +21 -21
  128. package/public/index.html +9 -9
  129. package/public/modules/header.tpl +13 -13
  130. package/public/modules/initial_state.tpl +54 -54
  131. package/scripts/preinstall.js +112 -112
  132. package/server/index.ts +680 -671
  133. package/server/model/connection.entity.ts +65 -65
  134. package/server/model/database.entity.ts +245 -245
  135. package/server/service/connection.service.ts +356 -341
  136. package/server/service/database/base.service.ts +406 -367
  137. package/server/service/database/cockroachdb.service.ts +871 -782
  138. package/server/service/database/database.service.ts +775 -638
  139. package/server/service/database/index.ts +6 -6
  140. package/server/service/database/mongodb.service.ts +39 -1
  141. package/server/service/database/mssql.service.ts +931 -840
  142. package/server/service/database/mysql.service.ts +1025 -890
  143. package/server/service/database/oracle.service.ts +1035 -959
  144. package/server/service/database/postgres.service.ts +960 -871
  145. package/server/service/database/sap.service.ts +89 -0
  146. package/server/service/database/sqlite.service.ts +787 -708
  147. package/server/service/session.service.ts +158 -158
  148. package/server/tsconfig.json +20 -20
  149. package/server.js +149 -149
  150. package/server.pid +1 -0
  151. package/src/adapter/ajax.ts +135 -135
  152. package/src/assets/base.css +1 -1
  153. package/src/assets/database.css +949 -949
  154. package/src/assets/images/svg/illustrations/illustration-1.svg +1 -1
  155. package/src/assets/images/svg/illustrations/illustration-2.svg +2 -2
  156. package/src/assets/images/svg/illustrations/illustration-3.svg +50 -50
  157. package/src/assets/images/svg/illustrations/illustration-4.svg +1 -1
  158. package/src/assets/images/svg/illustrations/illustration-5.svg +73 -73
  159. package/src/assets/images/svg/illustrations/illustration-6.svg +89 -89
  160. package/src/assets/images/svg/illustrations/illustration-7.svg +39 -39
  161. package/src/assets/images/svg/separators/curve-2.svg +3 -3
  162. package/src/assets/images/svg/separators/curve.svg +3 -3
  163. package/src/assets/images/svg/separators/line.svg +3 -3
  164. package/src/assets/logo.svg +73 -73
  165. package/src/assets/main.css +1 -1
  166. package/src/base/config.ts +20 -20
  167. package/src/base/detect.ts +134 -134
  168. package/src/base/entity.ts +92 -92
  169. package/src/base/eventBus.ts +36 -36
  170. package/src/components/connection-editor/index.vue +588 -588
  171. package/src/components/dataGrid/index.vue +104 -104
  172. package/src/components/dataGrid/pagination.vue +105 -105
  173. package/src/components/loading/index.vue +42 -42
  174. package/src/components/modal/index.ts +180 -180
  175. package/src/components/modal/index.vue +560 -560
  176. package/src/components/toast/index.ts +43 -43
  177. package/src/components/toast/toast.vue +57 -57
  178. package/src/components/user/name.vue +103 -103
  179. package/src/components/user/selector.vue +416 -416
  180. package/src/domain/SysConfig.ts +74 -74
  181. package/src/platform/App.vue +7 -7
  182. package/src/platform/database/components/connection-detail.vue +1153 -1154
  183. package/src/platform/database/components/data-editor.vue +477 -477
  184. package/src/platform/database/components/database-detail.vue +1173 -1172
  185. package/src/platform/database/components/database-monitor.vue +1085 -1085
  186. package/src/platform/database/components/db-tools.vue +1264 -816
  187. package/src/platform/database/components/query-history.vue +1348 -1348
  188. package/src/platform/database/components/sql-executor.vue +737 -737
  189. package/src/platform/database/components/sql-query-editor.vue +1045 -1045
  190. package/src/platform/database/components/table-detail.vue +1375 -1376
  191. package/src/platform/database/components/table-editor.vue +916 -916
  192. package/src/platform/database/explorer.vue +1839 -1839
  193. package/src/platform/database/index.vue +1192 -1192
  194. package/src/platform/database/layout.vue +366 -366
  195. package/src/platform/database/router.ts +36 -36
  196. package/src/platform/database/styles/common.scss +601 -601
  197. package/src/platform/database/types/common.ts +444 -444
  198. package/src/platform/database/utils/export.ts +231 -231
  199. package/src/platform/database/utils/helpers.ts +436 -436
  200. package/src/platform/index.ts +32 -32
  201. package/src/platform/router.ts +40 -40
  202. package/src/platform/vscode/bridge.ts +121 -121
  203. package/src/platform/vscode/components/ConnectionPanel.vue +272 -272
  204. package/src/platform/vscode/components/DatabasePanel.vue +532 -532
  205. package/src/platform/vscode/components/QueryPanel.vue +371 -371
  206. package/src/platform/vscode/entry/connection.ts +13 -13
  207. package/src/platform/vscode/entry/database.ts +13 -13
  208. package/src/platform/vscode/entry/query.ts +13 -13
  209. package/src/platform/vscode/index.ts +5 -5
  210. package/src/service/base.ts +133 -127
  211. package/src/service/database.ts +505 -495
  212. package/src/service/login.ts +120 -120
  213. package/src/shims-vue.d.ts +6 -6
  214. package/src/stores/connection.ts +266 -266
  215. package/src/stores/session.ts +87 -87
  216. package/src/typings/database-types.ts +412 -412
  217. package/src/typings/database.ts +363 -363
  218. package/src/typings/global.d.ts +58 -58
  219. package/src/typings/pinia.d.ts +7 -7
  220. package/src/utils/clipboard.ts +29 -29
  221. package/src/utils/database-types.ts +242 -242
  222. package/src/utils/modal.ts +123 -123
  223. package/src/utils/request.ts +55 -55
  224. package/src/utils/sleep.ts +3 -3
  225. package/src/utils/toast.ts +73 -73
  226. package/src/utils/util.ts +171 -171
  227. package/src/utils/xlsx.ts +228 -228
  228. package/tsconfig.json +33 -33
  229. package/view/index.html +9 -9
  230. package/view/modules/header.tpl +13 -13
  231. package/view/modules/initial_state.tpl +19 -19
  232. package/vite.config.ts +424 -424
  233. package/vite.config.vscode.ts +47 -47
  234. package/fdb2.server.pid +0 -1
  235. package/server/backups/db_ai_breakout_2026-03-11T08-38-48-677Z.sql +0 -0
@@ -1,738 +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
- }
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
738
  </style>