fdb2 1.0.7 → 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 (234) 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 +764 -226
  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/server/backups/db_ai_breakout_2026-03-11T08-38-48-677Z.sql +0 -0
@@ -1,437 +1,437 @@
1
- // 数据库平台通用工具函数
2
-
3
- // 格式化文件大小
4
- export function formatFileSize(bytes: number): string {
5
- if (bytes === 0) return '0 B';
6
- const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
7
- const i = Math.floor(Math.log(bytes) / Math.log(1024));
8
- return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
9
- }
10
-
11
- // 格式化时间
12
- export function formatTime(timestamp: Date): string {
13
- const now = new Date();
14
- const diff = now.getTime() - timestamp.getTime();
15
- const minutes = Math.floor(diff / 60000);
16
- const hours = Math.floor(diff / 3600000);
17
- const days = Math.floor(diff / 86400000);
18
-
19
- if (minutes < 1) return '刚刚';
20
- if (minutes < 60) return `${minutes}分钟前`;
21
- if (hours < 24) return `${hours}小时前`;
22
- if (days < 7) return `${days}天前`;
23
-
24
- return timestamp.toLocaleDateString('zh-CN');
25
- }
26
-
27
- // 格式化日期时间
28
- export function formatDateTime(date: Date): string {
29
- return date.toLocaleString('zh-CN', {
30
- year: 'numeric',
31
- month: '2-digit',
32
- day: '2-digit',
33
- hour: '2-digit',
34
- minute: '2-digit',
35
- second: '2-digit'
36
- });
37
- }
38
-
39
- // 格式化执行时间
40
- export function formatExecutionTime(ms: number): string {
41
- if (ms < 1000) return `${ms}ms`;
42
- if (ms < 60000) return `${(ms / 1000).toFixed(2)}s`;
43
- return `${(ms / 60000).toFixed(2)}min`;
44
- }
45
-
46
- // 获取执行时间状态类
47
- export function getExecutionTimeClass(time: number): string {
48
- if (time < 100) return 'fast';
49
- if (time < 1000) return 'normal';
50
- if (time < 2000) return 'slow';
51
- return 'very-slow';
52
- }
53
-
54
- // 截断SQL语句
55
- export function truncateSql(sql: string, maxLength = 80): string {
56
- if (sql.length <= maxLength) return sql;
57
- return sql.substring(0, maxLength) + '...';
58
- }
59
-
60
- // 格式化SQL语句
61
- export function formatSql(sql: string): string {
62
- return sql
63
- .replace(/\s+/g, ' ')
64
- .replace(/,/g, ',\n ')
65
- .replace(/\bFROM\b/gi, '\nFROM')
66
- .replace(/\bWHERE\b/gi, '\nWHERE')
67
- .replace(/\bORDER BY\b/gi, '\nORDER BY')
68
- .replace(/\bGROUP BY\b/gi, '\nGROUP BY')
69
- .replace(/\bHAVING\b/gi, '\nHAVING')
70
- .replace(/\bLIMIT\b/gi, '\nLIMIT')
71
- .trim();
72
- }
73
-
74
- // 获取SQL类型
75
- export function getSqlType(sql: string): string {
76
- const trimmed = sql.trim().toUpperCase();
77
- if (trimmed.startsWith('SELECT')) return 'SELECT';
78
- if (trimmed.startsWith('INSERT')) return 'INSERT';
79
- if (trimmed.startsWith('UPDATE')) return 'UPDATE';
80
- if (trimmed.startsWith('DELETE')) return 'DELETE';
81
- if (trimmed.startsWith('CREATE')) return 'CREATE';
82
- if (trimmed.startsWith('ALTER')) return 'ALTER';
83
- if (trimmed.startsWith('DROP')) return 'DROP';
84
- if (trimmed.startsWith('TRUNCATE')) return 'TRUNCATE';
85
- return 'OTHER';
86
- }
87
-
88
- // 验证SQL语句
89
- export function validateSql(sql: string): { valid: boolean; error?: string } {
90
- if (!sql || sql.trim() === '') {
91
- return { valid: false, error: 'SQL语句不能为空' };
92
- }
93
-
94
- // 基本的SQL注入检查
95
- const dangerousPatterns = [
96
- /DROP\s+DATABASE/i,
97
- /DROP\s+TABLE/i,
98
- /TRUNCATE/i,
99
- /DELETE\s+FROM.*WHERE\s+1\s*=\s*1/i,
100
- /UPDATE.*SET.*WHERE\s+1\s*=\s*1/i
101
- ];
102
-
103
- for (const pattern of dangerousPatterns) {
104
- if (pattern.test(sql)) {
105
- return { valid: false, error: '检测到潜在的危险SQL操作' };
106
- }
107
- }
108
-
109
- return { valid: true };
110
- }
111
-
112
- // 生成UUID
113
- export function generateUUID(): string {
114
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
115
- const r = Math.random() * 16 | 0;
116
- const v = c === 'x' ? r : (r & 0x3 | 0x8);
117
- return v.toString(16);
118
- });
119
- }
120
-
121
- // 深拷贝对象
122
- export function deepClone<T>(obj: T): T {
123
- if (obj === null || typeof obj !== 'object') return obj;
124
- if (obj instanceof Date) return new Date(obj.getTime()) as unknown as T;
125
- if (obj instanceof Array) return obj.map(item => deepClone(item)) as unknown as T;
126
- if (typeof obj === 'object') {
127
- const cloned = {} as T;
128
- for (const key in obj) {
129
- if (obj.hasOwnProperty(key)) {
130
- cloned[key] = deepClone(obj[key]);
131
- }
132
- }
133
- return cloned;
134
- }
135
- return obj;
136
- }
137
-
138
- // 防抖函数
139
- export function debounce<T extends (...args: any[]) => any>(
140
- func: T,
141
- wait: number
142
- ): (...args: Parameters<T>) => void {
143
- let timeout: NodeJS.Timeout;
144
- return (...args: Parameters<T>) => {
145
- clearTimeout(timeout);
146
- timeout = setTimeout(() => func(...args), wait);
147
- };
148
- }
149
-
150
- // 节流函数
151
- export function throttle<T extends (...args: any[]) => any>(
152
- func: T,
153
- wait: number
154
- ): (...args: Parameters<T>) => void {
155
- let inThrottle: boolean;
156
- return (...args: Parameters<T>) => {
157
- if (!inThrottle) {
158
- func(...args);
159
- inThrottle = true;
160
- setTimeout(() => inThrottle = false, wait);
161
- }
162
- };
163
- }
164
-
165
- // 复制到剪贴板
166
- export async function copyToClipboard(text: string): Promise<boolean> {
167
- try {
168
- await navigator.clipboard.writeText(text);
169
- return true;
170
- } catch (error) {
171
- // 降级方案
172
- const textArea = document.createElement('textarea');
173
- textArea.value = text;
174
- textArea.style.position = 'fixed';
175
- textArea.style.opacity = '0';
176
- document.body.appendChild(textArea);
177
- textArea.focus();
178
- textArea.select();
179
-
180
- try {
181
- document.execCommand('copy');
182
- document.body.removeChild(textArea);
183
- return true;
184
- } catch (error) {
185
- document.body.removeChild(textArea);
186
- return false;
187
- }
188
- }
189
- }
190
-
191
- // 下载文件
192
- export function downloadFile(content: string, filename: string, contentType = 'text/plain') {
193
- const blob = new Blob([content], { type: contentType });
194
- const url = URL.createObjectURL(blob);
195
- const link = document.createElement('a');
196
- link.href = url;
197
- link.download = filename;
198
- document.body.appendChild(link);
199
- link.click();
200
- document.body.removeChild(link);
201
- URL.revokeObjectURL(url);
202
- }
203
-
204
- // 导出为CSV
205
- export function exportToCSV(data: any[], filename: string) {
206
- if (data.length === 0) return;
207
-
208
- const headers = Object.keys(data[0]);
209
- const csvContent = [
210
- headers.join(','),
211
- ...data.map(row =>
212
- headers.map(header => {
213
- const value = row[header];
214
- if (value === null || value === undefined) return '';
215
- if (typeof value === 'object') return JSON.stringify(value);
216
- return `"${String(value).replace(/"/g, '""')}"`;
217
- }).join(',')
218
- )
219
- ].join('\n');
220
-
221
- downloadFile(csvContent, filename, 'text/csv;charset=utf-8;');
222
- }
223
-
224
- // 导出为JSON
225
- export function exportToJSON(data: any[], filename: string) {
226
- const jsonContent = JSON.stringify(data, null, 2);
227
- downloadFile(jsonContent, filename, 'application/json;charset=utf-8;');
228
- }
229
-
230
- // 获取数据库类型图标
231
- export function getDbTypeIcon(type: string): string {
232
- const iconMap: Record<string, string> = {
233
- mysql: 'bi-database',
234
- postgres: 'bi-database',
235
- postgresql: 'bi-database',
236
- sqlite: 'bi-database',
237
- mssql: 'bi-database',
238
- sqlserver: 'bi-database',
239
- oracle: 'bi-database',
240
- mongodb: 'bi-diagram-3'
241
- };
242
- return iconMap[type.toLowerCase()] || 'bi-database';
243
- }
244
-
245
- // 获取数据库类型标签
246
- export function getDbTypeLabel(type: string): string {
247
- const labelMap: Record<string, string> = {
248
- mysql: 'MySQL',
249
- postgres: 'PostgreSQL',
250
- postgresql: 'PostgreSQL',
251
- sqlite: 'SQLite',
252
- mssql: 'SQL Server',
253
- sqlserver: 'SQL Server',
254
- oracle: 'Oracle',
255
- mongodb: 'MongoDB'
256
- };
257
- return labelMap[type.toLowerCase()] || type;
258
- }
259
-
260
- // 获取数据库类型样式类
261
- export function getDbTypeClass(type: string): string {
262
- const classMap: Record<string, string> = {
263
- mysql: 'db-mysql',
264
- postgres: 'db-postgres',
265
- postgresql: 'db-postgres',
266
- sqlite: 'db-sqlite',
267
- mssql: 'db-mssql',
268
- sqlserver: 'db-mssql',
269
- oracle: 'db-oracle',
270
- mongodb: 'db-mongodb'
271
- };
272
- return classMap[type.toLowerCase()] || 'db-default';
273
- }
274
-
275
- // 计算百分比
276
- export function calculatePercentage(value: number, total: number): number {
277
- if (total === 0) return 0;
278
- return Math.round((value / total) * 100);
279
- }
280
-
281
- // 格式化数字
282
- export function formatNumber(num: number): string {
283
- if (num >= 1000000) {
284
- return (num / 1000000).toFixed(1) + 'M';
285
- }
286
- if (num >= 1000) {
287
- return (num / 1000).toFixed(1) + 'K';
288
- }
289
- return num.toString();
290
- }
291
-
292
- // 获取颜色值
293
- export function getColorByValue(value: number, ranges: { threshold: number; color: string }[]): string {
294
- for (const range of ranges) {
295
- if (value <= range.threshold) {
296
- return range.color;
297
- }
298
- }
299
- return ranges[ranges.length - 1].color;
300
- }
301
-
302
- // 创建Toast通知
303
- export function createToast(message: string, type: 'success' | 'error' | 'warning' | 'info' = 'info', duration = 3000) {
304
- const toast = document.createElement('div');
305
- toast.className = `toast toast-${type}`;
306
- toast.innerHTML = `
307
- <div class="toast-content">
308
- <i class="bi bi-${getToastIcon(type)}"></i>
309
- <span>${message}</span>
310
- </div>
311
- `;
312
-
313
- Object.assign(toast.style, {
314
- position: 'fixed',
315
- top: '20px',
316
- right: '20px',
317
- background: 'white',
318
- border: '1px solid #e5e7eb',
319
- borderRadius: '8px',
320
- padding: '1rem',
321
- boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
322
- zIndex: '9999',
323
- display: 'flex',
324
- alignItems: 'center',
325
- gap: '0.5rem',
326
- minWidth: '250px',
327
- transform: 'translateX(100%)',
328
- transition: 'transform 0.3s ease'
329
- });
330
-
331
- document.body.appendChild(toast);
332
-
333
- // 触发动画
334
- setTimeout(() => {
335
- toast.style.transform = 'translateX(0)';
336
- }, 100);
337
-
338
- // 自动移除
339
- setTimeout(() => {
340
- toast.style.transform = 'translateX(100%)';
341
- setTimeout(() => {
342
- if (toast.parentNode) {
343
- toast.parentNode.removeChild(toast);
344
- }
345
- }, 300);
346
- }, duration);
347
- }
348
-
349
- function getToastIcon(type: string): string {
350
- const iconMap: Record<string, string> = {
351
- success: 'check-circle',
352
- error: 'x-circle',
353
- warning: 'exclamation-triangle',
354
- info: 'info-circle'
355
- };
356
- return iconMap[type] || 'info-circle';
357
- }
358
-
359
- // 数组去重
360
- export function uniqueArray<T>(array: T[], key?: keyof T): T[] {
361
- if (!key) return [...new Set(array)];
362
-
363
- const seen = new Set();
364
- return array.filter(item => {
365
- const value = item[key];
366
- if (seen.has(value)) {
367
- return false;
368
- }
369
- seen.add(value);
370
- return true;
371
- });
372
- }
373
-
374
- // 数组分组
375
- export function groupBy<T>(array: T[], key: keyof T): Record<string, T[]> {
376
- return array.reduce((groups, item) => {
377
- const groupKey = String(item[key]);
378
- if (!groups[groupKey]) {
379
- groups[groupKey] = [];
380
- }
381
- groups[groupKey].push(item);
382
- return groups;
383
- }, {} as Record<string, T[]>);
384
- }
385
-
386
- // 数组排序
387
- export function sortBy<T>(array: T[], key: keyof T, direction: 'asc' | 'desc' = 'asc'): T[] {
388
- return [...array].sort((a, b) => {
389
- const aVal = a[key];
390
- const bVal = b[key];
391
-
392
- if (aVal === null || aVal === undefined) return direction === 'asc' ? 1 : -1;
393
- if (bVal === null || bVal === undefined) return direction === 'asc' ? -1 : 1;
394
-
395
- let comparison = 0;
396
- if (typeof aVal === 'number' && typeof bVal === 'number') {
397
- comparison = aVal - bVal;
398
- } else {
399
- comparison = String(aVal).localeCompare(String(bVal));
400
- }
401
-
402
- return direction === 'asc' ? comparison : -comparison;
403
- });
404
- }
405
-
406
- // 检查对象是否为空
407
- export function isEmpty(obj: any): boolean {
408
- if (obj == null) return true;
409
- if (Array.isArray(obj)) return obj.length === 0;
410
- if (typeof obj === 'object') return Object.keys(obj).length === 0;
411
- return false;
412
- }
413
-
414
- // 合并对象
415
- export function mergeObjects<T extends Record<string, any>>(...objects: Partial<T>[]): T {
416
- return objects.reduce((result, obj) => {
417
- return { ...result, ...obj };
418
- }, {} as T);
419
- }
420
-
421
- // 获取嵌套对象属性
422
- export function getNestedProperty(obj: any, path: string): any {
423
- return path.split('.').reduce((current, key) => current?.[key], obj);
424
- }
425
-
426
- // 设置嵌套对象属性
427
- export function setNestedProperty(obj: any, path: string, value: any): void {
428
- const keys = path.split('.');
429
- const lastKey = keys.pop()!;
430
- const target = keys.reduce((current, key) => {
431
- if (!current[key] || typeof current[key] !== 'object') {
432
- current[key] = {};
433
- }
434
- return current[key];
435
- }, obj);
436
- target[lastKey] = value;
1
+ // 数据库平台通用工具函数
2
+
3
+ // 格式化文件大小
4
+ export function formatFileSize(bytes: number): string {
5
+ if (bytes === 0) return '0 B';
6
+ const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
7
+ const i = Math.floor(Math.log(bytes) / Math.log(1024));
8
+ return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
9
+ }
10
+
11
+ // 格式化时间
12
+ export function formatTime(timestamp: Date): string {
13
+ const now = new Date();
14
+ const diff = now.getTime() - timestamp.getTime();
15
+ const minutes = Math.floor(diff / 60000);
16
+ const hours = Math.floor(diff / 3600000);
17
+ const days = Math.floor(diff / 86400000);
18
+
19
+ if (minutes < 1) return '刚刚';
20
+ if (minutes < 60) return `${minutes}分钟前`;
21
+ if (hours < 24) return `${hours}小时前`;
22
+ if (days < 7) return `${days}天前`;
23
+
24
+ return timestamp.toLocaleDateString('zh-CN');
25
+ }
26
+
27
+ // 格式化日期时间
28
+ export function formatDateTime(date: Date): string {
29
+ return date.toLocaleString('zh-CN', {
30
+ year: 'numeric',
31
+ month: '2-digit',
32
+ day: '2-digit',
33
+ hour: '2-digit',
34
+ minute: '2-digit',
35
+ second: '2-digit'
36
+ });
37
+ }
38
+
39
+ // 格式化执行时间
40
+ export function formatExecutionTime(ms: number): string {
41
+ if (ms < 1000) return `${ms}ms`;
42
+ if (ms < 60000) return `${(ms / 1000).toFixed(2)}s`;
43
+ return `${(ms / 60000).toFixed(2)}min`;
44
+ }
45
+
46
+ // 获取执行时间状态类
47
+ export function getExecutionTimeClass(time: number): string {
48
+ if (time < 100) return 'fast';
49
+ if (time < 1000) return 'normal';
50
+ if (time < 2000) return 'slow';
51
+ return 'very-slow';
52
+ }
53
+
54
+ // 截断SQL语句
55
+ export function truncateSql(sql: string, maxLength = 80): string {
56
+ if (sql.length <= maxLength) return sql;
57
+ return sql.substring(0, maxLength) + '...';
58
+ }
59
+
60
+ // 格式化SQL语句
61
+ export function formatSql(sql: string): string {
62
+ return sql
63
+ .replace(/\s+/g, ' ')
64
+ .replace(/,/g, ',\n ')
65
+ .replace(/\bFROM\b/gi, '\nFROM')
66
+ .replace(/\bWHERE\b/gi, '\nWHERE')
67
+ .replace(/\bORDER BY\b/gi, '\nORDER BY')
68
+ .replace(/\bGROUP BY\b/gi, '\nGROUP BY')
69
+ .replace(/\bHAVING\b/gi, '\nHAVING')
70
+ .replace(/\bLIMIT\b/gi, '\nLIMIT')
71
+ .trim();
72
+ }
73
+
74
+ // 获取SQL类型
75
+ export function getSqlType(sql: string): string {
76
+ const trimmed = sql.trim().toUpperCase();
77
+ if (trimmed.startsWith('SELECT')) return 'SELECT';
78
+ if (trimmed.startsWith('INSERT')) return 'INSERT';
79
+ if (trimmed.startsWith('UPDATE')) return 'UPDATE';
80
+ if (trimmed.startsWith('DELETE')) return 'DELETE';
81
+ if (trimmed.startsWith('CREATE')) return 'CREATE';
82
+ if (trimmed.startsWith('ALTER')) return 'ALTER';
83
+ if (trimmed.startsWith('DROP')) return 'DROP';
84
+ if (trimmed.startsWith('TRUNCATE')) return 'TRUNCATE';
85
+ return 'OTHER';
86
+ }
87
+
88
+ // 验证SQL语句
89
+ export function validateSql(sql: string): { valid: boolean; error?: string } {
90
+ if (!sql || sql.trim() === '') {
91
+ return { valid: false, error: 'SQL语句不能为空' };
92
+ }
93
+
94
+ // 基本的SQL注入检查
95
+ const dangerousPatterns = [
96
+ /DROP\s+DATABASE/i,
97
+ /DROP\s+TABLE/i,
98
+ /TRUNCATE/i,
99
+ /DELETE\s+FROM.*WHERE\s+1\s*=\s*1/i,
100
+ /UPDATE.*SET.*WHERE\s+1\s*=\s*1/i
101
+ ];
102
+
103
+ for (const pattern of dangerousPatterns) {
104
+ if (pattern.test(sql)) {
105
+ return { valid: false, error: '检测到潜在的危险SQL操作' };
106
+ }
107
+ }
108
+
109
+ return { valid: true };
110
+ }
111
+
112
+ // 生成UUID
113
+ export function generateUUID(): string {
114
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
115
+ const r = Math.random() * 16 | 0;
116
+ const v = c === 'x' ? r : (r & 0x3 | 0x8);
117
+ return v.toString(16);
118
+ });
119
+ }
120
+
121
+ // 深拷贝对象
122
+ export function deepClone<T>(obj: T): T {
123
+ if (obj === null || typeof obj !== 'object') return obj;
124
+ if (obj instanceof Date) return new Date(obj.getTime()) as unknown as T;
125
+ if (obj instanceof Array) return obj.map(item => deepClone(item)) as unknown as T;
126
+ if (typeof obj === 'object') {
127
+ const cloned = {} as T;
128
+ for (const key in obj) {
129
+ if (obj.hasOwnProperty(key)) {
130
+ cloned[key] = deepClone(obj[key]);
131
+ }
132
+ }
133
+ return cloned;
134
+ }
135
+ return obj;
136
+ }
137
+
138
+ // 防抖函数
139
+ export function debounce<T extends (...args: any[]) => any>(
140
+ func: T,
141
+ wait: number
142
+ ): (...args: Parameters<T>) => void {
143
+ let timeout: NodeJS.Timeout;
144
+ return (...args: Parameters<T>) => {
145
+ clearTimeout(timeout);
146
+ timeout = setTimeout(() => func(...args), wait);
147
+ };
148
+ }
149
+
150
+ // 节流函数
151
+ export function throttle<T extends (...args: any[]) => any>(
152
+ func: T,
153
+ wait: number
154
+ ): (...args: Parameters<T>) => void {
155
+ let inThrottle: boolean;
156
+ return (...args: Parameters<T>) => {
157
+ if (!inThrottle) {
158
+ func(...args);
159
+ inThrottle = true;
160
+ setTimeout(() => inThrottle = false, wait);
161
+ }
162
+ };
163
+ }
164
+
165
+ // 复制到剪贴板
166
+ export async function copyToClipboard(text: string): Promise<boolean> {
167
+ try {
168
+ await navigator.clipboard.writeText(text);
169
+ return true;
170
+ } catch (error) {
171
+ // 降级方案
172
+ const textArea = document.createElement('textarea');
173
+ textArea.value = text;
174
+ textArea.style.position = 'fixed';
175
+ textArea.style.opacity = '0';
176
+ document.body.appendChild(textArea);
177
+ textArea.focus();
178
+ textArea.select();
179
+
180
+ try {
181
+ document.execCommand('copy');
182
+ document.body.removeChild(textArea);
183
+ return true;
184
+ } catch (error) {
185
+ document.body.removeChild(textArea);
186
+ return false;
187
+ }
188
+ }
189
+ }
190
+
191
+ // 下载文件
192
+ export function downloadFile(content: string, filename: string, contentType = 'text/plain') {
193
+ const blob = new Blob([content], { type: contentType });
194
+ const url = URL.createObjectURL(blob);
195
+ const link = document.createElement('a');
196
+ link.href = url;
197
+ link.download = filename;
198
+ document.body.appendChild(link);
199
+ link.click();
200
+ document.body.removeChild(link);
201
+ URL.revokeObjectURL(url);
202
+ }
203
+
204
+ // 导出为CSV
205
+ export function exportToCSV(data: any[], filename: string) {
206
+ if (data.length === 0) return;
207
+
208
+ const headers = Object.keys(data[0]);
209
+ const csvContent = [
210
+ headers.join(','),
211
+ ...data.map(row =>
212
+ headers.map(header => {
213
+ const value = row[header];
214
+ if (value === null || value === undefined) return '';
215
+ if (typeof value === 'object') return JSON.stringify(value);
216
+ return `"${String(value).replace(/"/g, '""')}"`;
217
+ }).join(',')
218
+ )
219
+ ].join('\n');
220
+
221
+ downloadFile(csvContent, filename, 'text/csv;charset=utf-8;');
222
+ }
223
+
224
+ // 导出为JSON
225
+ export function exportToJSON(data: any[], filename: string) {
226
+ const jsonContent = JSON.stringify(data, null, 2);
227
+ downloadFile(jsonContent, filename, 'application/json;charset=utf-8;');
228
+ }
229
+
230
+ // 获取数据库类型图标
231
+ export function getDbTypeIcon(type: string): string {
232
+ const iconMap: Record<string, string> = {
233
+ mysql: 'bi-database',
234
+ postgres: 'bi-database',
235
+ postgresql: 'bi-database',
236
+ sqlite: 'bi-database',
237
+ mssql: 'bi-database',
238
+ sqlserver: 'bi-database',
239
+ oracle: 'bi-database',
240
+ mongodb: 'bi-diagram-3'
241
+ };
242
+ return iconMap[type.toLowerCase()] || 'bi-database';
243
+ }
244
+
245
+ // 获取数据库类型标签
246
+ export function getDbTypeLabel(type: string): string {
247
+ const labelMap: Record<string, string> = {
248
+ mysql: 'MySQL',
249
+ postgres: 'PostgreSQL',
250
+ postgresql: 'PostgreSQL',
251
+ sqlite: 'SQLite',
252
+ mssql: 'SQL Server',
253
+ sqlserver: 'SQL Server',
254
+ oracle: 'Oracle',
255
+ mongodb: 'MongoDB'
256
+ };
257
+ return labelMap[type.toLowerCase()] || type;
258
+ }
259
+
260
+ // 获取数据库类型样式类
261
+ export function getDbTypeClass(type: string): string {
262
+ const classMap: Record<string, string> = {
263
+ mysql: 'db-mysql',
264
+ postgres: 'db-postgres',
265
+ postgresql: 'db-postgres',
266
+ sqlite: 'db-sqlite',
267
+ mssql: 'db-mssql',
268
+ sqlserver: 'db-mssql',
269
+ oracle: 'db-oracle',
270
+ mongodb: 'db-mongodb'
271
+ };
272
+ return classMap[type.toLowerCase()] || 'db-default';
273
+ }
274
+
275
+ // 计算百分比
276
+ export function calculatePercentage(value: number, total: number): number {
277
+ if (total === 0) return 0;
278
+ return Math.round((value / total) * 100);
279
+ }
280
+
281
+ // 格式化数字
282
+ export function formatNumber(num: number): string {
283
+ if (num >= 1000000) {
284
+ return (num / 1000000).toFixed(1) + 'M';
285
+ }
286
+ if (num >= 1000) {
287
+ return (num / 1000).toFixed(1) + 'K';
288
+ }
289
+ return num.toString();
290
+ }
291
+
292
+ // 获取颜色值
293
+ export function getColorByValue(value: number, ranges: { threshold: number; color: string }[]): string {
294
+ for (const range of ranges) {
295
+ if (value <= range.threshold) {
296
+ return range.color;
297
+ }
298
+ }
299
+ return ranges[ranges.length - 1].color;
300
+ }
301
+
302
+ // 创建Toast通知
303
+ export function createToast(message: string, type: 'success' | 'error' | 'warning' | 'info' = 'info', duration = 3000) {
304
+ const toast = document.createElement('div');
305
+ toast.className = `toast toast-${type}`;
306
+ toast.innerHTML = `
307
+ <div class="toast-content">
308
+ <i class="bi bi-${getToastIcon(type)}"></i>
309
+ <span>${message}</span>
310
+ </div>
311
+ `;
312
+
313
+ Object.assign(toast.style, {
314
+ position: 'fixed',
315
+ top: '20px',
316
+ right: '20px',
317
+ background: 'white',
318
+ border: '1px solid #e5e7eb',
319
+ borderRadius: '8px',
320
+ padding: '1rem',
321
+ boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
322
+ zIndex: '9999',
323
+ display: 'flex',
324
+ alignItems: 'center',
325
+ gap: '0.5rem',
326
+ minWidth: '250px',
327
+ transform: 'translateX(100%)',
328
+ transition: 'transform 0.3s ease'
329
+ });
330
+
331
+ document.body.appendChild(toast);
332
+
333
+ // 触发动画
334
+ setTimeout(() => {
335
+ toast.style.transform = 'translateX(0)';
336
+ }, 100);
337
+
338
+ // 自动移除
339
+ setTimeout(() => {
340
+ toast.style.transform = 'translateX(100%)';
341
+ setTimeout(() => {
342
+ if (toast.parentNode) {
343
+ toast.parentNode.removeChild(toast);
344
+ }
345
+ }, 300);
346
+ }, duration);
347
+ }
348
+
349
+ function getToastIcon(type: string): string {
350
+ const iconMap: Record<string, string> = {
351
+ success: 'check-circle',
352
+ error: 'x-circle',
353
+ warning: 'exclamation-triangle',
354
+ info: 'info-circle'
355
+ };
356
+ return iconMap[type] || 'info-circle';
357
+ }
358
+
359
+ // 数组去重
360
+ export function uniqueArray<T>(array: T[], key?: keyof T): T[] {
361
+ if (!key) return [...new Set(array)];
362
+
363
+ const seen = new Set();
364
+ return array.filter(item => {
365
+ const value = item[key];
366
+ if (seen.has(value)) {
367
+ return false;
368
+ }
369
+ seen.add(value);
370
+ return true;
371
+ });
372
+ }
373
+
374
+ // 数组分组
375
+ export function groupBy<T>(array: T[], key: keyof T): Record<string, T[]> {
376
+ return array.reduce((groups, item) => {
377
+ const groupKey = String(item[key]);
378
+ if (!groups[groupKey]) {
379
+ groups[groupKey] = [];
380
+ }
381
+ groups[groupKey].push(item);
382
+ return groups;
383
+ }, {} as Record<string, T[]>);
384
+ }
385
+
386
+ // 数组排序
387
+ export function sortBy<T>(array: T[], key: keyof T, direction: 'asc' | 'desc' = 'asc'): T[] {
388
+ return [...array].sort((a, b) => {
389
+ const aVal = a[key];
390
+ const bVal = b[key];
391
+
392
+ if (aVal === null || aVal === undefined) return direction === 'asc' ? 1 : -1;
393
+ if (bVal === null || bVal === undefined) return direction === 'asc' ? -1 : 1;
394
+
395
+ let comparison = 0;
396
+ if (typeof aVal === 'number' && typeof bVal === 'number') {
397
+ comparison = aVal - bVal;
398
+ } else {
399
+ comparison = String(aVal).localeCompare(String(bVal));
400
+ }
401
+
402
+ return direction === 'asc' ? comparison : -comparison;
403
+ });
404
+ }
405
+
406
+ // 检查对象是否为空
407
+ export function isEmpty(obj: any): boolean {
408
+ if (obj == null) return true;
409
+ if (Array.isArray(obj)) return obj.length === 0;
410
+ if (typeof obj === 'object') return Object.keys(obj).length === 0;
411
+ return false;
412
+ }
413
+
414
+ // 合并对象
415
+ export function mergeObjects<T extends Record<string, any>>(...objects: Partial<T>[]): T {
416
+ return objects.reduce((result, obj) => {
417
+ return { ...result, ...obj };
418
+ }, {} as T);
419
+ }
420
+
421
+ // 获取嵌套对象属性
422
+ export function getNestedProperty(obj: any, path: string): any {
423
+ return path.split('.').reduce((current, key) => current?.[key], obj);
424
+ }
425
+
426
+ // 设置嵌套对象属性
427
+ export function setNestedProperty(obj: any, path: string, value: any): void {
428
+ const keys = path.split('.');
429
+ const lastKey = keys.pop()!;
430
+ const target = keys.reduce((current, key) => {
431
+ if (!current[key] || typeof current[key] !== 'object') {
432
+ current[key] = {};
433
+ }
434
+ return current[key];
435
+ }, obj);
436
+ target[lastKey] = value;
437
437
  }