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.
- package/.dockerignore +21 -21
- package/.editorconfig +11 -11
- package/.eslintrc.cjs +14 -14
- package/.eslintrc.json +7 -7
- package/.prettierrc.js +3 -3
- package/.tpl.env +21 -21
- package/.vscodeignore +45 -45
- package/README.md +312 -312
- package/bin/build.sh +28 -28
- package/bin/deploy.sh +8 -8
- package/bin/dev.sh +10 -10
- package/bin/docker/dev-docker-compose.yml +43 -43
- package/bin/docker/dev.Dockerfile +24 -24
- package/bin/docker/prod-docker-compose.yml +17 -17
- package/bin/docker/prod.Dockerfile +29 -29
- package/bin/fdb2.js +220 -220
- package/dist/package.json +29 -29
- package/dist/pnpm-lock.yaml +1042 -354
- package/dist/public/explorer.css +1464 -1437
- package/dist/public/explorer.js +759 -223
- package/dist/public/index.css +1026 -1026
- package/dist/public/index.js +15 -9
- package/dist/public/layout.css +221 -221
- package/dist/public/layout.js +1 -1
- package/dist/public/vue.js +8 -2
- package/dist/scripts/preinstall.js +112 -112
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +8 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.ts +680 -671
- package/dist/server/model/connection.entity.ts +65 -65
- package/dist/server/model/database.entity.ts +245 -245
- package/dist/server/service/connection.service.d.ts +6 -1
- package/dist/server/service/connection.service.d.ts.map +1 -1
- package/dist/server/service/connection.service.js +15 -0
- package/dist/server/service/connection.service.js.map +1 -1
- package/dist/server/service/connection.service.ts +356 -341
- package/dist/server/service/database/base.service.d.ts +27 -0
- package/dist/server/service/database/base.service.d.ts.map +1 -1
- package/dist/server/service/database/base.service.js +17 -0
- package/dist/server/service/database/base.service.js.map +1 -1
- package/dist/server/service/database/base.service.ts +406 -367
- package/dist/server/service/database/cockroachdb.service.d.ts +16 -0
- package/dist/server/service/database/cockroachdb.service.d.ts.map +1 -1
- package/dist/server/service/database/cockroachdb.service.js +220 -154
- package/dist/server/service/database/cockroachdb.service.js.map +1 -1
- package/dist/server/service/database/cockroachdb.service.ts +871 -782
- package/dist/server/service/database/database.service.d.ts +4 -0
- package/dist/server/service/database/database.service.d.ts.map +1 -1
- package/dist/server/service/database/database.service.js +123 -0
- package/dist/server/service/database/database.service.js.map +1 -1
- package/dist/server/service/database/database.service.ts +775 -638
- package/dist/server/service/database/index.ts +6 -6
- package/dist/server/service/database/mongodb.service.d.ts +16 -0
- package/dist/server/service/database/mongodb.service.d.ts.map +1 -1
- package/dist/server/service/database/mongodb.service.js +35 -0
- package/dist/server/service/database/mongodb.service.js.map +1 -1
- package/dist/server/service/database/mongodb.service.ts +39 -1
- package/dist/server/service/database/mssql.service.d.ts +16 -0
- package/dist/server/service/database/mssql.service.d.ts.map +1 -1
- package/dist/server/service/database/mssql.service.js +168 -96
- package/dist/server/service/database/mssql.service.js.map +1 -1
- package/dist/server/service/database/mssql.service.ts +931 -840
- package/dist/server/service/database/mysql.service.d.ts +16 -0
- package/dist/server/service/database/mysql.service.d.ts.map +1 -1
- package/dist/server/service/database/mysql.service.js +189 -80
- package/dist/server/service/database/mysql.service.js.map +1 -1
- package/dist/server/service/database/mysql.service.ts +1025 -890
- package/dist/server/service/database/oracle.service.d.ts +16 -0
- package/dist/server/service/database/oracle.service.d.ts.map +1 -1
- package/dist/server/service/database/oracle.service.js +182 -120
- package/dist/server/service/database/oracle.service.js.map +1 -1
- package/dist/server/service/database/oracle.service.ts +1035 -959
- package/dist/server/service/database/postgres.service.d.ts +16 -0
- package/dist/server/service/database/postgres.service.d.ts.map +1 -1
- package/dist/server/service/database/postgres.service.js +154 -88
- package/dist/server/service/database/postgres.service.js.map +1 -1
- package/dist/server/service/database/postgres.service.ts +960 -871
- package/dist/server/service/database/sap.service.d.ts +16 -0
- package/dist/server/service/database/sap.service.d.ts.map +1 -1
- package/dist/server/service/database/sap.service.js +66 -0
- package/dist/server/service/database/sap.service.js.map +1 -1
- package/dist/server/service/database/sap.service.ts +89 -0
- package/dist/server/service/database/sqlite.service.d.ts +16 -0
- package/dist/server/service/database/sqlite.service.d.ts.map +1 -1
- package/dist/server/service/database/sqlite.service.js +77 -18
- package/dist/server/service/database/sqlite.service.js.map +1 -1
- package/dist/server/service/database/sqlite.service.ts +787 -708
- package/dist/server/service/session.service.ts +158 -158
- package/dist/view/index.html +38 -38
- package/env.d.ts +1 -1
- package/package.json +1 -1
- package/packages/vscode/.vscodeignore +44 -44
- package/packages/vscode/README.md +62 -62
- package/packages/vscode/out/database-services/cockroachdb.service.js +154 -154
- package/packages/vscode/out/database-services/mssql.service.js +96 -96
- package/packages/vscode/out/database-services/mysql.service.js +80 -80
- package/packages/vscode/out/database-services/oracle.service.js +120 -120
- package/packages/vscode/out/database-services/postgres.service.js +88 -88
- package/packages/vscode/out/database-services/sqlite.service.js +18 -18
- package/packages/vscode/out/provider/WebViewProvider.js +32 -32
- package/packages/vscode/package.json +142 -142
- package/packages/vscode/resources/icon.svg +5 -5
- package/packages/vscode/resources/webview/connection.css +41 -41
- package/packages/vscode/resources/webview/database.css +163 -163
- package/packages/vscode/resources/webview/index.html +9 -9
- package/packages/vscode/resources/webview/modules/header.tpl +13 -13
- package/packages/vscode/resources/webview/modules/initial_state.tpl +54 -54
- package/packages/vscode/resources/webview/query.css +104 -104
- package/packages/vscode/src/database-services/base.service.ts +362 -362
- package/packages/vscode/src/database-services/cockroachdb.service.ts +659 -659
- package/packages/vscode/src/database-services/connection.service.ts +340 -340
- package/packages/vscode/src/database-services/database.service.ts +629 -629
- package/packages/vscode/src/database-services/index.ts +6 -6
- package/packages/vscode/src/database-services/model/connection.entity.ts +65 -65
- package/packages/vscode/src/database-services/model/database.entity.ts +245 -245
- package/packages/vscode/src/database-services/mssql.service.ts +722 -722
- package/packages/vscode/src/database-services/mysql.service.ts +760 -760
- package/packages/vscode/src/database-services/oracle.service.ts +831 -831
- package/packages/vscode/src/database-services/postgres.service.ts +740 -740
- package/packages/vscode/src/database-services/sqlite.service.ts +558 -558
- package/packages/vscode/src/extension.ts +76 -76
- package/packages/vscode/src/provider/DatabaseTreeProvider.ts +167 -167
- package/packages/vscode/src/provider/WebViewProvider.ts +277 -277
- package/packages/vscode/src/service/DatabaseServiceBridge.ts +414 -414
- package/packages/vscode/src/typings/connection.ts +90 -90
- package/packages/vscode/tsconfig.json +21 -21
- package/public/index.html +9 -9
- package/public/modules/header.tpl +13 -13
- package/public/modules/initial_state.tpl +54 -54
- package/scripts/preinstall.js +112 -112
- package/server/index.ts +680 -671
- package/server/model/connection.entity.ts +65 -65
- package/server/model/database.entity.ts +245 -245
- package/server/service/connection.service.ts +356 -341
- package/server/service/database/base.service.ts +406 -367
- package/server/service/database/cockroachdb.service.ts +871 -782
- package/server/service/database/database.service.ts +775 -638
- package/server/service/database/index.ts +6 -6
- package/server/service/database/mongodb.service.ts +39 -1
- package/server/service/database/mssql.service.ts +931 -840
- package/server/service/database/mysql.service.ts +1025 -890
- package/server/service/database/oracle.service.ts +1035 -959
- package/server/service/database/postgres.service.ts +960 -871
- package/server/service/database/sap.service.ts +89 -0
- package/server/service/database/sqlite.service.ts +787 -708
- package/server/service/session.service.ts +158 -158
- package/server/tsconfig.json +20 -20
- package/server.js +149 -149
- package/server.pid +1 -0
- package/src/adapter/ajax.ts +135 -135
- package/src/assets/base.css +1 -1
- package/src/assets/database.css +949 -949
- package/src/assets/images/svg/illustrations/illustration-1.svg +1 -1
- package/src/assets/images/svg/illustrations/illustration-2.svg +2 -2
- package/src/assets/images/svg/illustrations/illustration-3.svg +50 -50
- package/src/assets/images/svg/illustrations/illustration-4.svg +1 -1
- package/src/assets/images/svg/illustrations/illustration-5.svg +73 -73
- package/src/assets/images/svg/illustrations/illustration-6.svg +89 -89
- package/src/assets/images/svg/illustrations/illustration-7.svg +39 -39
- package/src/assets/images/svg/separators/curve-2.svg +3 -3
- package/src/assets/images/svg/separators/curve.svg +3 -3
- package/src/assets/images/svg/separators/line.svg +3 -3
- package/src/assets/logo.svg +73 -73
- package/src/assets/main.css +1 -1
- package/src/base/config.ts +20 -20
- package/src/base/detect.ts +134 -134
- package/src/base/entity.ts +92 -92
- package/src/base/eventBus.ts +36 -36
- package/src/components/connection-editor/index.vue +588 -588
- package/src/components/dataGrid/index.vue +104 -104
- package/src/components/dataGrid/pagination.vue +105 -105
- package/src/components/loading/index.vue +42 -42
- package/src/components/modal/index.ts +180 -180
- package/src/components/modal/index.vue +560 -560
- package/src/components/toast/index.ts +43 -43
- package/src/components/toast/toast.vue +57 -57
- package/src/components/user/name.vue +103 -103
- package/src/components/user/selector.vue +416 -416
- package/src/domain/SysConfig.ts +74 -74
- package/src/platform/App.vue +7 -7
- package/src/platform/database/components/connection-detail.vue +1153 -1154
- package/src/platform/database/components/data-editor.vue +477 -477
- package/src/platform/database/components/database-detail.vue +1173 -1172
- package/src/platform/database/components/database-monitor.vue +1085 -1085
- package/src/platform/database/components/db-tools.vue +1264 -816
- package/src/platform/database/components/query-history.vue +1348 -1348
- package/src/platform/database/components/sql-executor.vue +737 -737
- package/src/platform/database/components/sql-query-editor.vue +1045 -1045
- package/src/platform/database/components/table-detail.vue +1375 -1376
- package/src/platform/database/components/table-editor.vue +916 -916
- package/src/platform/database/explorer.vue +1839 -1839
- package/src/platform/database/index.vue +1192 -1192
- package/src/platform/database/layout.vue +366 -366
- package/src/platform/database/router.ts +36 -36
- package/src/platform/database/styles/common.scss +601 -601
- package/src/platform/database/types/common.ts +444 -444
- package/src/platform/database/utils/export.ts +231 -231
- package/src/platform/database/utils/helpers.ts +436 -436
- package/src/platform/index.ts +32 -32
- package/src/platform/router.ts +40 -40
- package/src/platform/vscode/bridge.ts +121 -121
- package/src/platform/vscode/components/ConnectionPanel.vue +272 -272
- package/src/platform/vscode/components/DatabasePanel.vue +532 -532
- package/src/platform/vscode/components/QueryPanel.vue +371 -371
- package/src/platform/vscode/entry/connection.ts +13 -13
- package/src/platform/vscode/entry/database.ts +13 -13
- package/src/platform/vscode/entry/query.ts +13 -13
- package/src/platform/vscode/index.ts +5 -5
- package/src/service/base.ts +133 -127
- package/src/service/database.ts +505 -495
- package/src/service/login.ts +120 -120
- package/src/shims-vue.d.ts +6 -6
- package/src/stores/connection.ts +266 -266
- package/src/stores/session.ts +87 -87
- package/src/typings/database-types.ts +412 -412
- package/src/typings/database.ts +363 -363
- package/src/typings/global.d.ts +58 -58
- package/src/typings/pinia.d.ts +7 -7
- package/src/utils/clipboard.ts +29 -29
- package/src/utils/database-types.ts +242 -242
- package/src/utils/modal.ts +123 -123
- package/src/utils/request.ts +55 -55
- package/src/utils/sleep.ts +3 -3
- package/src/utils/toast.ts +73 -73
- package/src/utils/util.ts +171 -171
- package/src/utils/xlsx.ts +228 -228
- package/tsconfig.json +33 -33
- package/view/index.html +9 -9
- package/view/modules/header.tpl +13 -13
- package/view/modules/initial_state.tpl +19 -19
- package/vite.config.ts +424 -424
- package/vite.config.vscode.ts +47 -47
- package/fdb2.server.pid +0 -1
- package/server/backups/db_ai_breakout_2026-03-11T08-38-48-677Z.sql +0 -0
|
@@ -1,817 +1,1265 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="db-tools">
|
|
3
|
-
<div class="tools-header">
|
|
4
|
-
<h5 class="tools-title">
|
|
5
|
-
<i class="bi bi-tools"></i>
|
|
6
|
-
数据库管理工具
|
|
7
|
-
</h5>
|
|
8
|
-
</div>
|
|
9
|
-
|
|
10
|
-
<div class="tools-content">
|
|
11
|
-
<!-- 数据备份 -->
|
|
12
|
-
<div class="tool-section">
|
|
13
|
-
<h6 class="section-title">
|
|
14
|
-
<i class="bi bi-shield-check"></i>
|
|
15
|
-
数据备份
|
|
16
|
-
</h6>
|
|
17
|
-
<div class="tool-actions">
|
|
18
|
-
<button class="btn btn-outline-primary btn-sm" @click="backupDatabase">
|
|
19
|
-
<i class="bi bi-download"></i> 备份数据库
|
|
20
|
-
</button>
|
|
21
|
-
<button class="btn btn-outline-secondary btn-sm" @click="showRestoreModal">
|
|
22
|
-
<i class="bi bi-upload"></i> 恢复数据库
|
|
23
|
-
</button>
|
|
24
|
-
<button class="btn btn-outline-info btn-sm" @click="showScheduleModal">
|
|
25
|
-
<i class="bi bi-clock"></i> 定时备份
|
|
26
|
-
</button>
|
|
27
|
-
</div>
|
|
28
|
-
</div>
|
|
29
|
-
|
|
30
|
-
<!-- 用户管理 -->
|
|
31
|
-
<div class="tool-section">
|
|
32
|
-
<h6 class="section-title">
|
|
33
|
-
<i class="bi bi-people"></i>
|
|
34
|
-
用户管理
|
|
35
|
-
</h6>
|
|
36
|
-
<div class="tool-actions">
|
|
37
|
-
<button class="btn btn-outline-success btn-sm" @click="showUsersList">
|
|
38
|
-
<i class="bi bi-person-lines-fill"></i> 用户列表
|
|
39
|
-
</button>
|
|
40
|
-
<button class="btn btn-outline-primary btn-sm" @click="showCreateUserModal">
|
|
41
|
-
<i class="bi bi-person-plus"></i> 创建用户
|
|
42
|
-
</button>
|
|
43
|
-
<button class="btn btn-outline-warning btn-sm" @click="showPermissionsModal">
|
|
44
|
-
<i class="bi bi-key"></i> 权限管理
|
|
45
|
-
</button>
|
|
46
|
-
</div>
|
|
47
|
-
</div>
|
|
48
|
-
|
|
49
|
-
<!-- 性能监控 -->
|
|
50
|
-
<div class="tool-section">
|
|
51
|
-
<h6 class="section-title">
|
|
52
|
-
<i class="bi bi-speedometer2"></i>
|
|
53
|
-
性能监控
|
|
54
|
-
</h6>
|
|
55
|
-
<div class="tool-actions">
|
|
56
|
-
<button class="btn btn-outline-info btn-sm" @click="showProcessList">
|
|
57
|
-
<i class="bi bi-activity"></i> 进程列表
|
|
58
|
-
</button>
|
|
59
|
-
<button class="btn btn-outline-warning btn-sm" @click="showSlowQueries">
|
|
60
|
-
<i class="bi bi-hourglass-split"></i> 慢查询
|
|
61
|
-
</button>
|
|
62
|
-
<button class="btn btn-outline-danger btn-sm" @click="showConnectionsList">
|
|
63
|
-
<i class="bi bi-diagram-3"></i> 连接数
|
|
64
|
-
</button>
|
|
65
|
-
</div>
|
|
66
|
-
</div>
|
|
67
|
-
|
|
68
|
-
<!-- 数据库优化 -->
|
|
69
|
-
<div class="tool-section">
|
|
70
|
-
<h6 class="section-title">
|
|
71
|
-
<i class="bi bi-gear-wide-connected"></i>
|
|
72
|
-
数据库优化
|
|
73
|
-
</h6>
|
|
74
|
-
<div class="tool-actions">
|
|
75
|
-
<button class="btn btn-outline-success btn-sm" @click="optimizeDatabase">
|
|
76
|
-
<i class="bi bi-lightning-charge"></i> 优化数据库
|
|
77
|
-
</button>
|
|
78
|
-
<button class="btn btn-outline-primary btn-sm" @click="analyzeTables">
|
|
79
|
-
<i class="bi bi-search"></i> 分析表
|
|
80
|
-
</button>
|
|
81
|
-
<button class="btn btn-outline-secondary btn-sm" @click="repairTables">
|
|
82
|
-
<i class="bi bi-tools"></i> 修复表
|
|
83
|
-
</button>
|
|
84
|
-
<button class="btn btn-outline-info btn-sm" @click="clearLogs">
|
|
85
|
-
<i class="bi bi-trash"></i> 清理日志
|
|
86
|
-
</button>
|
|
87
|
-
</div>
|
|
88
|
-
</div>
|
|
89
|
-
|
|
90
|
-
<!-- 数据迁移 -->
|
|
91
|
-
<div class="tool-section">
|
|
92
|
-
<h6 class="section-title">
|
|
93
|
-
<i class="bi bi-arrow-left-right"></i>
|
|
94
|
-
数据迁移
|
|
95
|
-
</h6>
|
|
96
|
-
<div class="tool-actions">
|
|
97
|
-
<button class="btn btn-outline-primary btn-sm" @click="showExportModal">
|
|
98
|
-
<i class="bi bi-box-arrow-up-right"></i> 导出结构
|
|
99
|
-
</button>
|
|
100
|
-
<button class="btn btn-outline-success btn-sm" @click="showImportModal">
|
|
101
|
-
<i class="bi bi-box-arrow-in-down"></i> 导入数据
|
|
102
|
-
</button>
|
|
103
|
-
<button class="btn btn-outline-warning btn-sm" @click="
|
|
104
|
-
<i class="bi bi-arrow-repeat"></i> 数据同步
|
|
105
|
-
</button>
|
|
106
|
-
</div>
|
|
107
|
-
</div>
|
|
108
|
-
|
|
109
|
-
<!-- 健康检查 -->
|
|
110
|
-
<div class="tool-section">
|
|
111
|
-
<h6 class="section-title">
|
|
112
|
-
<i class="bi bi-heart-pulse"></i>
|
|
113
|
-
健康检查
|
|
114
|
-
</h6>
|
|
115
|
-
<div class="tool-actions">
|
|
116
|
-
<button class="btn btn-outline-info btn-sm" @click="runHealthCheck">
|
|
117
|
-
<i class="bi bi-clipboard-check"></i> 健康检查
|
|
118
|
-
</button>
|
|
119
|
-
<button class="btn btn-outline-secondary btn-sm" @click="showStatistics">
|
|
120
|
-
<i class="bi bi-bar-chart"></i> 数据统计
|
|
121
|
-
</button>
|
|
122
|
-
<button class="btn btn-outline-warning btn-sm" @click="showAuditLog">
|
|
123
|
-
<i class="bi bi-journal-text"></i> 审计日志
|
|
124
|
-
</button>
|
|
125
|
-
</div>
|
|
126
|
-
</div>
|
|
127
|
-
</div>
|
|
128
|
-
|
|
129
|
-
<!--
|
|
130
|
-
<div class="
|
|
131
|
-
<div class="
|
|
132
|
-
<h6 class="
|
|
133
|
-
<i class="
|
|
134
|
-
|
|
135
|
-
</h6>
|
|
136
|
-
<button class="btn btn-outline-secondary btn-sm" @click="
|
|
137
|
-
<i class="bi bi-
|
|
138
|
-
</button>
|
|
139
|
-
</div>
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
<div class="
|
|
147
|
-
<div class="
|
|
148
|
-
<
|
|
149
|
-
<
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
<
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
</
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
function
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
.
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
.
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
.
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
.
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
.
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
.
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
.
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
.
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
}
|
|
1
|
+
<template>
|
|
2
|
+
<div class="db-tools">
|
|
3
|
+
<div class="tools-header">
|
|
4
|
+
<h5 class="tools-title">
|
|
5
|
+
<i class="bi bi-tools"></i>
|
|
6
|
+
数据库管理工具
|
|
7
|
+
</h5>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<div class="tools-content">
|
|
11
|
+
<!-- 数据备份 -->
|
|
12
|
+
<div class="tool-section">
|
|
13
|
+
<h6 class="section-title">
|
|
14
|
+
<i class="bi bi-shield-check"></i>
|
|
15
|
+
数据备份
|
|
16
|
+
</h6>
|
|
17
|
+
<div class="tool-actions">
|
|
18
|
+
<button class="btn btn-outline-primary btn-sm" @click="backupDatabase">
|
|
19
|
+
<i class="bi bi-download"></i> 备份数据库
|
|
20
|
+
</button>
|
|
21
|
+
<button class="btn btn-outline-secondary btn-sm" @click="showRestoreModal">
|
|
22
|
+
<i class="bi bi-upload"></i> 恢复数据库
|
|
23
|
+
</button>
|
|
24
|
+
<button class="btn btn-outline-info btn-sm" @click="showScheduleModal">
|
|
25
|
+
<i class="bi bi-clock"></i> 定时备份
|
|
26
|
+
</button>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<!-- 用户管理 -->
|
|
31
|
+
<div class="tool-section">
|
|
32
|
+
<h6 class="section-title">
|
|
33
|
+
<i class="bi bi-people"></i>
|
|
34
|
+
用户管理
|
|
35
|
+
</h6>
|
|
36
|
+
<div class="tool-actions">
|
|
37
|
+
<button class="btn btn-outline-success btn-sm" @click="showUsersList">
|
|
38
|
+
<i class="bi bi-person-lines-fill"></i> 用户列表
|
|
39
|
+
</button>
|
|
40
|
+
<button class="btn btn-outline-primary btn-sm" @click="showCreateUserModal">
|
|
41
|
+
<i class="bi bi-person-plus"></i> 创建用户
|
|
42
|
+
</button>
|
|
43
|
+
<button class="btn btn-outline-warning btn-sm" @click="showPermissionsModal">
|
|
44
|
+
<i class="bi bi-key"></i> 权限管理
|
|
45
|
+
</button>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<!-- 性能监控 -->
|
|
50
|
+
<div class="tool-section">
|
|
51
|
+
<h6 class="section-title">
|
|
52
|
+
<i class="bi bi-speedometer2"></i>
|
|
53
|
+
性能监控
|
|
54
|
+
</h6>
|
|
55
|
+
<div class="tool-actions">
|
|
56
|
+
<button class="btn btn-outline-info btn-sm" @click="showProcessList">
|
|
57
|
+
<i class="bi bi-activity"></i> 进程列表
|
|
58
|
+
</button>
|
|
59
|
+
<button class="btn btn-outline-warning btn-sm" @click="showSlowQueries">
|
|
60
|
+
<i class="bi bi-hourglass-split"></i> 慢查询
|
|
61
|
+
</button>
|
|
62
|
+
<button class="btn btn-outline-danger btn-sm" @click="showConnectionsList">
|
|
63
|
+
<i class="bi bi-diagram-3"></i> 连接数
|
|
64
|
+
</button>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<!-- 数据库优化 -->
|
|
69
|
+
<div class="tool-section">
|
|
70
|
+
<h6 class="section-title">
|
|
71
|
+
<i class="bi bi-gear-wide-connected"></i>
|
|
72
|
+
数据库优化
|
|
73
|
+
</h6>
|
|
74
|
+
<div class="tool-actions">
|
|
75
|
+
<button class="btn btn-outline-success btn-sm" @click="optimizeDatabase">
|
|
76
|
+
<i class="bi bi-lightning-charge"></i> 优化数据库
|
|
77
|
+
</button>
|
|
78
|
+
<button class="btn btn-outline-primary btn-sm" @click="analyzeTables">
|
|
79
|
+
<i class="bi bi-search"></i> 分析表
|
|
80
|
+
</button>
|
|
81
|
+
<button class="btn btn-outline-secondary btn-sm" @click="repairTables">
|
|
82
|
+
<i class="bi bi-tools"></i> 修复表
|
|
83
|
+
</button>
|
|
84
|
+
<button class="btn btn-outline-info btn-sm" @click="clearLogs">
|
|
85
|
+
<i class="bi bi-trash"></i> 清理日志
|
|
86
|
+
</button>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<!-- 数据迁移 -->
|
|
91
|
+
<div class="tool-section">
|
|
92
|
+
<h6 class="section-title">
|
|
93
|
+
<i class="bi bi-arrow-left-right"></i>
|
|
94
|
+
数据迁移
|
|
95
|
+
</h6>
|
|
96
|
+
<div class="tool-actions">
|
|
97
|
+
<button class="btn btn-outline-primary btn-sm" @click="showExportModal">
|
|
98
|
+
<i class="bi bi-box-arrow-up-right"></i> 导出结构
|
|
99
|
+
</button>
|
|
100
|
+
<button class="btn btn-outline-success btn-sm" @click="showImportModal">
|
|
101
|
+
<i class="bi bi-box-arrow-in-down"></i> 导入数据
|
|
102
|
+
</button>
|
|
103
|
+
<button class="btn btn-outline-warning btn-sm" @click="selectTool('sync')">
|
|
104
|
+
<i class="bi bi-arrow-repeat"></i> 数据同步
|
|
105
|
+
</button>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
<!-- 健康检查 -->
|
|
110
|
+
<div class="tool-section">
|
|
111
|
+
<h6 class="section-title">
|
|
112
|
+
<i class="bi bi-heart-pulse"></i>
|
|
113
|
+
健康检查
|
|
114
|
+
</h6>
|
|
115
|
+
<div class="tool-actions">
|
|
116
|
+
<button class="btn btn-outline-info btn-sm" @click="runHealthCheck">
|
|
117
|
+
<i class="bi bi-clipboard-check"></i> 健康检查
|
|
118
|
+
</button>
|
|
119
|
+
<button class="btn btn-outline-secondary btn-sm" @click="showStatistics">
|
|
120
|
+
<i class="bi bi-bar-chart"></i> 数据统计
|
|
121
|
+
</button>
|
|
122
|
+
<button class="btn btn-outline-warning btn-sm" @click="showAuditLog">
|
|
123
|
+
<i class="bi bi-journal-text"></i> 审计日志
|
|
124
|
+
</button>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
<!-- 工具组件展示区域 -->
|
|
130
|
+
<div class="tool-component-area" v-if="selectedTool">
|
|
131
|
+
<div class="component-header">
|
|
132
|
+
<h6 class="component-title">
|
|
133
|
+
<i :class="getToolIcon(selectedTool)"></i>
|
|
134
|
+
{{ getToolTitle(selectedTool) }}
|
|
135
|
+
</h6>
|
|
136
|
+
<button class="btn btn-outline-secondary btn-sm" @click="closeTool">
|
|
137
|
+
<i class="bi bi-x"></i> 关闭
|
|
138
|
+
</button>
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<!-- 数据同步组件 -->
|
|
142
|
+
<div v-if="selectedTool === 'sync'" class="tool-component sync-component">
|
|
143
|
+
<!-- 源数据库配置 -->
|
|
144
|
+
<div class="mb-4">
|
|
145
|
+
<h6 class="text-primary mb-2"><i class="bi bi-database"></i> 源数据库</h6>
|
|
146
|
+
<div class="row g-3">
|
|
147
|
+
<div class="col-md-6">
|
|
148
|
+
<label class="form-label">数据库名称</label>
|
|
149
|
+
<input type="text" class="form-control" v-model="syncConfig.source.database" readonly>
|
|
150
|
+
</div>
|
|
151
|
+
<div class="col-md-6">
|
|
152
|
+
<label class="form-label">选择表</label>
|
|
153
|
+
<select class="form-select" v-model="syncConfig.source.tableName">
|
|
154
|
+
<option value="">请选择表</option>
|
|
155
|
+
<option v-for="table in tables" :key="table.name" :value="table.name">{{ table.name }}</option>
|
|
156
|
+
</select>
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
|
|
161
|
+
<!-- 目标数据库配置 -->
|
|
162
|
+
<div class="mb-4">
|
|
163
|
+
<h6 class="text-primary mb-2"><i class="bi bi-database"></i> 目标数据库</h6>
|
|
164
|
+
|
|
165
|
+
<!-- 连接模式选择 -->
|
|
166
|
+
<div class="mb-3">
|
|
167
|
+
<div class="form-check form-switch">
|
|
168
|
+
<input class="form-check-input" type="checkbox" v-model="useExistingConnection" id="useExistingConnection">
|
|
169
|
+
<label class="form-check-label" for="useExistingConnection">使用已配置的数据库连接</label>
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
|
|
173
|
+
<!-- 已配置连接选择 -->
|
|
174
|
+
<div v-if="useExistingConnection" class="row g-3">
|
|
175
|
+
<div class="col-md-6">
|
|
176
|
+
<label class="form-label">选择数据库连接</label>
|
|
177
|
+
<select class="form-select" v-model="selectedConnectionId">
|
|
178
|
+
<option value="">请选择连接</option>
|
|
179
|
+
<option v-for="conn in connections" :key="conn.id" :value="conn.id">{{ conn.name }} ({{ conn.type }})</option>
|
|
180
|
+
</select>
|
|
181
|
+
</div>
|
|
182
|
+
<div class="col-md-6">
|
|
183
|
+
<label class="form-label">选择数据库</label>
|
|
184
|
+
<select class="form-select" v-model="selectedDatabaseName">
|
|
185
|
+
<option value="">请选择数据库</option>
|
|
186
|
+
<option v-for="db in databases" :key="db" :value="db">{{ db }}</option>
|
|
187
|
+
</select>
|
|
188
|
+
</div>
|
|
189
|
+
<div class="col-md-12">
|
|
190
|
+
<label class="form-label">目标表名</label>
|
|
191
|
+
<input type="text" class="form-control" v-model="syncConfig.target.tableName">
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
<!-- 手动配置 -->
|
|
196
|
+
<div v-else class="row g-3">
|
|
197
|
+
<div class="col-md-4">
|
|
198
|
+
<label class="form-label">数据库类型</label>
|
|
199
|
+
<select class="form-select" v-model="syncConfig.target.dbType">
|
|
200
|
+
<option value="mysql">MySQL</option>
|
|
201
|
+
<option value="postgresql">PostgreSQL</option>
|
|
202
|
+
<option value="sqlite">SQLite</option>
|
|
203
|
+
<option value="sqlserver">SQL Server</option>
|
|
204
|
+
<option value="oracle">Oracle</option>
|
|
205
|
+
</select>
|
|
206
|
+
</div>
|
|
207
|
+
<div class="col-md-4">
|
|
208
|
+
<label class="form-label">主机</label>
|
|
209
|
+
<input type="text" class="form-control" v-model="syncConfig.target.host">
|
|
210
|
+
</div>
|
|
211
|
+
<div class="col-md-4">
|
|
212
|
+
<label class="form-label">端口</label>
|
|
213
|
+
<input type="number" class="form-control" v-model="syncConfig.target.port">
|
|
214
|
+
</div>
|
|
215
|
+
<div class="col-md-4">
|
|
216
|
+
<label class="form-label">数据库名</label>
|
|
217
|
+
<input type="text" class="form-control" v-model="syncConfig.target.database">
|
|
218
|
+
</div>
|
|
219
|
+
<div class="col-md-4">
|
|
220
|
+
<label class="form-label">用户名</label>
|
|
221
|
+
<input type="text" class="form-control" v-model="syncConfig.target.username">
|
|
222
|
+
</div>
|
|
223
|
+
<div class="col-md-4">
|
|
224
|
+
<label class="form-label">密码</label>
|
|
225
|
+
<input type="password" class="form-control" v-model="syncConfig.target.password">
|
|
226
|
+
</div>
|
|
227
|
+
<div class="col-md-6">
|
|
228
|
+
<label class="form-label">目标表名</label>
|
|
229
|
+
<input type="text" class="form-control" v-model="syncConfig.target.tableName">
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
</div>
|
|
233
|
+
|
|
234
|
+
<!-- 同步选项 -->
|
|
235
|
+
<div class="mb-4">
|
|
236
|
+
<h6 class="text-primary mb-2"><i class="bi bi-sliders"></i> 同步选项</h6>
|
|
237
|
+
<div class="row g-3">
|
|
238
|
+
<div class="col-md-6">
|
|
239
|
+
<div class="form-check">
|
|
240
|
+
<input type="checkbox" class="form-check-input" v-model="syncConfig.options.syncStructure" id="syncStructure">
|
|
241
|
+
<label class="form-check-label" for="syncStructure">同步表结构</label>
|
|
242
|
+
</div>
|
|
243
|
+
</div>
|
|
244
|
+
<div class="col-md-6">
|
|
245
|
+
<div class="form-check">
|
|
246
|
+
<input type="checkbox" class="form-check-input" v-model="syncConfig.options.syncData" id="syncData">
|
|
247
|
+
<label class="form-check-label" for="syncData">同步表数据</label>
|
|
248
|
+
</div>
|
|
249
|
+
</div>
|
|
250
|
+
<div class="col-md-6">
|
|
251
|
+
<div class="form-check">
|
|
252
|
+
<input type="checkbox" class="form-check-input" v-model="syncConfig.options.dropIfExists" id="dropIfExists">
|
|
253
|
+
<label class="form-check-label" for="dropIfExists">目标表存在时删除</label>
|
|
254
|
+
</div>
|
|
255
|
+
</div>
|
|
256
|
+
<div class="col-md-6">
|
|
257
|
+
<div class="form-check">
|
|
258
|
+
<input type="checkbox" class="form-check-input" v-model="syncConfig.options.bulkInsert" id="bulkInsert">
|
|
259
|
+
<label class="form-check-label" for="bulkInsert">批量插入数据</label>
|
|
260
|
+
</div>
|
|
261
|
+
</div>
|
|
262
|
+
<div class="col-md-6">
|
|
263
|
+
<div class="form-check">
|
|
264
|
+
<input type="checkbox" class="form-check-input" v-model="syncConfig.options.overrideExisting" id="overrideExisting">
|
|
265
|
+
<label class="form-check-label" for="overrideExisting">覆盖已存在的数据</label>
|
|
266
|
+
</div>
|
|
267
|
+
</div>
|
|
268
|
+
</div>
|
|
269
|
+
</div>
|
|
270
|
+
|
|
271
|
+
<!-- 操作按钮 -->
|
|
272
|
+
<div class="tool-actions">
|
|
273
|
+
<button class="btn btn-primary btn-sm" @click="performSync" :disabled="syncing || !isSyncFormValid">
|
|
274
|
+
<i class="bi bi-play-fill"></i> 开始同步
|
|
275
|
+
</button>
|
|
276
|
+
<button v-if="syncing" class="btn btn-outline-danger btn-sm" @click="stopSync">
|
|
277
|
+
<i class="bi bi-stop-fill"></i> 停止同步
|
|
278
|
+
</button>
|
|
279
|
+
</div>
|
|
280
|
+
</div>
|
|
281
|
+
</div>
|
|
282
|
+
|
|
283
|
+
<!-- 执行结果展示区域 -->
|
|
284
|
+
<div class="execution-results">
|
|
285
|
+
<div class="results-header">
|
|
286
|
+
<h6 class="results-title">
|
|
287
|
+
<i class="bi bi-terminal"></i>
|
|
288
|
+
执行结果
|
|
289
|
+
</h6>
|
|
290
|
+
<button class="btn btn-outline-secondary btn-sm" @click="clearResults">
|
|
291
|
+
<i class="bi bi-trash"></i> 清空
|
|
292
|
+
</button>
|
|
293
|
+
</div>
|
|
294
|
+
<div class="results-content" ref="resultsContentRef">
|
|
295
|
+
<div v-if="executionResults.length === 0" class="no-results">
|
|
296
|
+
<i class="bi bi-inbox"></i>
|
|
297
|
+
<p>暂无执行结果</p>
|
|
298
|
+
</div>
|
|
299
|
+
<div v-for="(result, index) in executionResults" :key="index" class="result-item" :class="`result-${result.status}`">
|
|
300
|
+
<div class="result-header" @click="toggleResult(index)">
|
|
301
|
+
<div class="result-title">
|
|
302
|
+
<i :class="getResultIcon(result.status)"></i>
|
|
303
|
+
<span class="operation-name">{{ result.operation }}</span>
|
|
304
|
+
<span class="operation-time">{{ result.timestamp }}</span>
|
|
305
|
+
</div>
|
|
306
|
+
<i class="bi bi-chevron-down toggle-icon" :class="{ 'expanded': result.expanded }"></i>
|
|
307
|
+
</div>
|
|
308
|
+
<div v-if="result.expanded" class="result-body">
|
|
309
|
+
<pre><code v-html="highlightJson(result.data)"></code></pre>
|
|
310
|
+
</div>
|
|
311
|
+
</div>
|
|
312
|
+
</div>
|
|
313
|
+
</div>
|
|
314
|
+
|
|
315
|
+
<!-- 数据恢复模态框 -->
|
|
316
|
+
<div class="modal fade" :class="{ show: restoreModalVisible }" :style="{ display: restoreModalVisible ? 'block' : 'none', zIndex: 1055 }">
|
|
317
|
+
<div class="modal-dialog">
|
|
318
|
+
<div class="modal-content">
|
|
319
|
+
<div class="modal-header">
|
|
320
|
+
<h5 class="modal-title">恢复数据库</h5>
|
|
321
|
+
<button type="button" class="btn-close" @click="closeRestoreModal"></button>
|
|
322
|
+
</div>
|
|
323
|
+
<div class="modal-body">
|
|
324
|
+
<p>请选择要恢复的备份文件:</p>
|
|
325
|
+
<div class="mb-3">
|
|
326
|
+
<input type="file" class="form-control" @change="handleFileChange" accept=".sql,.bak">
|
|
327
|
+
</div>
|
|
328
|
+
<div v-if="selectedFile" class="alert alert-info">
|
|
329
|
+
已选择文件:{{ selectedFile.name }}
|
|
330
|
+
</div>
|
|
331
|
+
<div class="mb-3 form-check">
|
|
332
|
+
<input type="checkbox" class="form-check-input" v-model="restoreOptions.dropExisting" id="dropExisting">
|
|
333
|
+
<label class="form-check-label" for="dropExisting">删除现有表</label>
|
|
334
|
+
</div>
|
|
335
|
+
</div>
|
|
336
|
+
<div class="modal-footer">
|
|
337
|
+
<button type="button" class="btn btn-secondary" @click="closeRestoreModal">取消</button>
|
|
338
|
+
<button type="button" class="btn btn-primary" @click="performRestore" :disabled="!selectedFile">
|
|
339
|
+
<span v-if="restoring" class="spinner-border spinner-border-sm me-2"></span>
|
|
340
|
+
恢复
|
|
341
|
+
</button>
|
|
342
|
+
</div>
|
|
343
|
+
</div>
|
|
344
|
+
</div>
|
|
345
|
+
</div>
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
</div>
|
|
349
|
+
</template>
|
|
350
|
+
|
|
351
|
+
<script lang="ts" setup>
|
|
352
|
+
import { ref, computed, watch, onMounted } from 'vue';
|
|
353
|
+
import { DatabaseService, ConnectionService } from '@/service/database';
|
|
354
|
+
import { modal } from '@/utils/modal';
|
|
355
|
+
import { toast } from '@/utils/toast';
|
|
356
|
+
|
|
357
|
+
const connectionService = new ConnectionService();
|
|
358
|
+
|
|
359
|
+
const props = defineProps<{
|
|
360
|
+
connection: any;
|
|
361
|
+
database: string;
|
|
362
|
+
}>();
|
|
363
|
+
|
|
364
|
+
const emit = defineEmits<{
|
|
365
|
+
'execute-sql': [sql: string];
|
|
366
|
+
}>();
|
|
367
|
+
|
|
368
|
+
const databaseService = new DatabaseService();
|
|
369
|
+
|
|
370
|
+
// 状态管理
|
|
371
|
+
const restoreModalVisible = ref(false);
|
|
372
|
+
const selectedFile = ref<File | null>(null);
|
|
373
|
+
const restoring = ref(false);
|
|
374
|
+
const resultsContentRef = ref<HTMLElement | null>(null);
|
|
375
|
+
|
|
376
|
+
// 工具组件状态
|
|
377
|
+
const selectedTool = ref<string | null>(null);
|
|
378
|
+
|
|
379
|
+
// 同步功能状态
|
|
380
|
+
const syncing = ref(false);
|
|
381
|
+
const tables = ref<any[]>([]);
|
|
382
|
+
const connections = ref<any[]>([]);
|
|
383
|
+
const databases = ref<any[]>([]);
|
|
384
|
+
const useExistingConnection = ref(false);
|
|
385
|
+
const selectedConnectionId = ref('');
|
|
386
|
+
const selectedDatabaseName = ref('');
|
|
387
|
+
|
|
388
|
+
// 同步配置
|
|
389
|
+
const syncConfig = ref({
|
|
390
|
+
source: {
|
|
391
|
+
database: '',
|
|
392
|
+
tableName: ''
|
|
393
|
+
},
|
|
394
|
+
target: {
|
|
395
|
+
dbType: 'mysql',
|
|
396
|
+
host: 'localhost',
|
|
397
|
+
port: 3306,
|
|
398
|
+
database: '',
|
|
399
|
+
username: 'root',
|
|
400
|
+
password: '',
|
|
401
|
+
tableName: ''
|
|
402
|
+
},
|
|
403
|
+
options: {
|
|
404
|
+
syncStructure: true,
|
|
405
|
+
syncData: true,
|
|
406
|
+
dropIfExists: false,
|
|
407
|
+
bulkInsert: true,
|
|
408
|
+
overrideExisting: false
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
// 监听源表名变化,自动更新目标表名
|
|
413
|
+
watch(() => syncConfig.value.source.tableName, (newTableName) => {
|
|
414
|
+
if (newTableName) {
|
|
415
|
+
syncConfig.value.target.tableName = newTableName;
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
// 组件挂载时初始化同步数据
|
|
420
|
+
onMounted(() => {
|
|
421
|
+
initSyncData();
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
// 监听连接ID变化,加载数据库列表
|
|
425
|
+
async function loadDatabases(connectionId: string) {
|
|
426
|
+
if (!connectionId) {
|
|
427
|
+
databases.value = [];
|
|
428
|
+
selectedDatabaseName.value = '';
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
try {
|
|
433
|
+
const res = await databaseService.getDatabases(connectionId);
|
|
434
|
+
if (res.ret === 0) {
|
|
435
|
+
databases.value = res.data || [];
|
|
436
|
+
} else {
|
|
437
|
+
databases.value = [];
|
|
438
|
+
}
|
|
439
|
+
selectedDatabaseName.value = '';
|
|
440
|
+
} catch (error) {
|
|
441
|
+
console.error('加载数据库列表失败:', error);
|
|
442
|
+
databases.value = [];
|
|
443
|
+
selectedDatabaseName.value = '';
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// 监听连接ID变化
|
|
448
|
+
watch(selectedConnectionId, (newVal) => {
|
|
449
|
+
loadDatabases(newVal);
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
// 执行结果历史
|
|
453
|
+
interface ExecutionResult {
|
|
454
|
+
operation: string;
|
|
455
|
+
status: 'success' | 'error' | 'info';
|
|
456
|
+
timestamp: string;
|
|
457
|
+
data: any;
|
|
458
|
+
expanded: boolean;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const executionResults = ref<ExecutionResult[]>([]);
|
|
462
|
+
|
|
463
|
+
const restoreOptions = ref({
|
|
464
|
+
dropExisting: false
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
// 验证同步表单
|
|
468
|
+
const isSyncFormValid = computed(() => {
|
|
469
|
+
if (useExistingConnection.value) {
|
|
470
|
+
return syncConfig.value.source.tableName &&
|
|
471
|
+
selectedConnectionId.value &&
|
|
472
|
+
selectedDatabaseName.value &&
|
|
473
|
+
syncConfig.value.target.tableName &&
|
|
474
|
+
(syncConfig.value.options.syncStructure || syncConfig.value.options.syncData);
|
|
475
|
+
} else {
|
|
476
|
+
return syncConfig.value.source.tableName &&
|
|
477
|
+
syncConfig.value.target.host &&
|
|
478
|
+
syncConfig.value.target.port &&
|
|
479
|
+
syncConfig.value.target.database &&
|
|
480
|
+
syncConfig.value.target.username &&
|
|
481
|
+
syncConfig.value.target.tableName &&
|
|
482
|
+
(syncConfig.value.options.syncStructure || syncConfig.value.options.syncData);
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
// 添加执行结果
|
|
487
|
+
function addExecutionResult(operation: string, status: 'success' | 'error' | 'info', data: any) {
|
|
488
|
+
const timestamp = new Date().toLocaleString('zh-CN', {
|
|
489
|
+
year: 'numeric',
|
|
490
|
+
month: '2-digit',
|
|
491
|
+
day: '2-digit',
|
|
492
|
+
hour: '2-digit',
|
|
493
|
+
minute: '2-digit',
|
|
494
|
+
second: '2-digit'
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
executionResults.value.unshift({
|
|
498
|
+
operation,
|
|
499
|
+
status,
|
|
500
|
+
timestamp,
|
|
501
|
+
data,
|
|
502
|
+
expanded: false
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
// 只保留最近50条记录
|
|
506
|
+
if (executionResults.value.length > 50) {
|
|
507
|
+
executionResults.value = executionResults.value.slice(0, 50);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// 自动滚动到底部(显示最新结果在顶部,所以滚动到0)
|
|
511
|
+
setTimeout(() => {
|
|
512
|
+
if (resultsContentRef.value) {
|
|
513
|
+
resultsContentRef.value.scrollTop = 0;
|
|
514
|
+
}
|
|
515
|
+
}, 100);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// 清空执行结果
|
|
519
|
+
function clearResults() {
|
|
520
|
+
executionResults.value = [];
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// 切换结果展开/收起
|
|
524
|
+
function toggleResult(index: number) {
|
|
525
|
+
const result = executionResults.value[index];
|
|
526
|
+
if (result) {
|
|
527
|
+
result.expanded = !result.expanded;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// 格式化错误信息
|
|
532
|
+
function formatError(error: any): any {
|
|
533
|
+
const formatted: any = {
|
|
534
|
+
success: false,
|
|
535
|
+
message: error.msg || error.message || '未知错误'
|
|
536
|
+
};
|
|
537
|
+
if (error.stack) {
|
|
538
|
+
formatted.stack = error.stack;
|
|
539
|
+
}
|
|
540
|
+
return formatted;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// JSON 语法高亮
|
|
544
|
+
function highlightJson(data: any): string {
|
|
545
|
+
if (data === null || data === undefined) return '';
|
|
546
|
+
const jsonStr = JSON.stringify(data, null, 2);
|
|
547
|
+
return jsonStr.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
|
|
548
|
+
let cls = 'json-number';
|
|
549
|
+
if (/^"/.test(match)) {
|
|
550
|
+
if (/:$/.test(match)) {
|
|
551
|
+
cls = 'json-key';
|
|
552
|
+
} else {
|
|
553
|
+
cls = 'json-string';
|
|
554
|
+
}
|
|
555
|
+
} else if (/true|false/.test(match)) {
|
|
556
|
+
cls = 'json-boolean';
|
|
557
|
+
} else if (/null/.test(match)) {
|
|
558
|
+
cls = 'json-null';
|
|
559
|
+
}
|
|
560
|
+
return '<span class="' + cls + '">' + match + '</span>';
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// 获取结果图标
|
|
565
|
+
function getResultIcon(status: string): string {
|
|
566
|
+
switch (status) {
|
|
567
|
+
case 'success':
|
|
568
|
+
return 'bi bi-check-circle-fill text-success';
|
|
569
|
+
case 'error':
|
|
570
|
+
return 'bi bi-x-circle-fill text-danger';
|
|
571
|
+
case 'info':
|
|
572
|
+
return 'bi bi-info-circle-fill text-info';
|
|
573
|
+
default:
|
|
574
|
+
return 'bi bi-dash-circle-fill text-secondary';
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// 数据备份
|
|
579
|
+
async function backupDatabase() {
|
|
580
|
+
const operation = '备份数据库';
|
|
581
|
+
try {
|
|
582
|
+
const res = await databaseService.backupDatabase(props.connection?.id || '', props.database);
|
|
583
|
+
if(res.ret === 0) {
|
|
584
|
+
addExecutionResult(operation, 'success', res);
|
|
585
|
+
} else {
|
|
586
|
+
modal.error(res.msg || '备份失败');
|
|
587
|
+
addExecutionResult(operation, 'error', formatError(res));
|
|
588
|
+
}
|
|
589
|
+
} catch (error: any) {
|
|
590
|
+
console.error('备份失败:', error);
|
|
591
|
+
modal.error(error.msg || error.message || '备份失败');
|
|
592
|
+
addExecutionResult(operation, 'error', formatError(error));
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// 用户管理
|
|
597
|
+
function showUsersList() {
|
|
598
|
+
addExecutionResult('用户列表', 'info', { message: '用户列表功能开发中...' });
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function showCreateUserModal() {
|
|
602
|
+
addExecutionResult('创建用户', 'info', { message: '创建用户功能开发中...' });
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function showPermissionsModal() {
|
|
606
|
+
addExecutionResult('权限管理', 'info', { message: '权限管理功能开发中...' });
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// 性能监控
|
|
610
|
+
function showProcessList() {
|
|
611
|
+
const sql = 'SHOW PROCESSLIST';
|
|
612
|
+
addExecutionResult('进程列表', 'info', { sql: sql, message: '已发送 SQL 查询' });
|
|
613
|
+
emit('execute-sql', sql);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function showSlowQueries() {
|
|
617
|
+
const sql = 'SELECT * FROM mysql.slow_log ORDER BY start_time DESC LIMIT 10';
|
|
618
|
+
addExecutionResult('慢查询', 'info', { sql: sql, message: '已发送 SQL 查询' });
|
|
619
|
+
emit('execute-sql', sql);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function showConnectionsList() {
|
|
623
|
+
const sql = 'SHOW STATUS LIKE "Threads_connected"';
|
|
624
|
+
addExecutionResult('连接数', 'info', { sql: sql, message: '已发送 SQL 查询' });
|
|
625
|
+
emit('execute-sql', sql);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// 数据库优化
|
|
629
|
+
async function optimizeDatabase() {
|
|
630
|
+
const operation = '优化数据库';
|
|
631
|
+
try {
|
|
632
|
+
const res = await databaseService.optimizeDatabase(props.connection?.id || '', props.database);
|
|
633
|
+
if(res.ret === 0) {
|
|
634
|
+
addExecutionResult(operation, 'success', res.data);
|
|
635
|
+
} else {
|
|
636
|
+
modal.error(res.msg || '优化失败');
|
|
637
|
+
addExecutionResult(operation, 'error', formatError(res));
|
|
638
|
+
}
|
|
639
|
+
} catch (error: any) {
|
|
640
|
+
console.error('优化失败:', error);
|
|
641
|
+
modal.error(error.msg || error.message || '优化失败');
|
|
642
|
+
addExecutionResult(operation, 'error', formatError(error));
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
async function analyzeTables() {
|
|
647
|
+
const operation = '分析表';
|
|
648
|
+
try {
|
|
649
|
+
const res = await databaseService.analyzeTables(props.connection?.id || '', props.database);
|
|
650
|
+
if(res.ret === 0) {
|
|
651
|
+
addExecutionResult(operation, 'success', res.data);
|
|
652
|
+
} else {
|
|
653
|
+
modal.error(res.msg || '分析失败');
|
|
654
|
+
addExecutionResult(operation, 'error', formatError(res));
|
|
655
|
+
}
|
|
656
|
+
} catch (error: any) {
|
|
657
|
+
console.error('分析失败:', error);
|
|
658
|
+
modal.error(res.msg || error.message || '分析失败');
|
|
659
|
+
addExecutionResult(operation, 'error', formatError(error));
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
async function repairTables() {
|
|
664
|
+
const operation = '修复表';
|
|
665
|
+
try {
|
|
666
|
+
const res = await databaseService.repairTables(props.connection?.id || '', props.database);
|
|
667
|
+
if(res.ret === 0) {
|
|
668
|
+
addExecutionResult(operation, 'success', res.data);
|
|
669
|
+
} else {
|
|
670
|
+
modal.error(res.msg || '修复失败');
|
|
671
|
+
addExecutionResult(operation, 'error', formatError(res));
|
|
672
|
+
}
|
|
673
|
+
} catch (error: any) {
|
|
674
|
+
console.error('修复失败:', error);
|
|
675
|
+
modal.error(res.msg || error.message || '修复失败');
|
|
676
|
+
addExecutionResult(operation, 'error', formatError(error));
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
async function clearLogs() {
|
|
681
|
+
const operation = '清理日志';
|
|
682
|
+
const logs = [
|
|
683
|
+
'TRUNCATE TABLE mysql.slow_log',
|
|
684
|
+
'TRUNCATE TABLE mysql.general_log',
|
|
685
|
+
'FLUSH LOGS'
|
|
686
|
+
];
|
|
687
|
+
|
|
688
|
+
logs.forEach(sql => {
|
|
689
|
+
addExecutionResult(`清理日志 - ${sql.split(' ')[1]}`, 'info', { sql, message: '已发送 SQL 查询' });
|
|
690
|
+
emit('execute-sql', sql);
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// 数据迁移
|
|
695
|
+
function showExportModal() {
|
|
696
|
+
addExecutionResult('导出结构', 'info', { message: '导出结构功能开发中...' });
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
function showImportModal() {
|
|
700
|
+
addExecutionResult('导入数据', 'info', { message: '导入数据功能开发中...' });
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// 健康检查
|
|
704
|
+
async function runHealthCheck() {
|
|
705
|
+
const operation = '健康检查';
|
|
706
|
+
const checks = [
|
|
707
|
+
{ name: '连接状态', sql: 'SELECT 1 as status' },
|
|
708
|
+
{ name: '表完整性', sql: 'SELECT COUNT(*) as status FROM information_schema.tables WHERE table_schema = DATABASE() AND table_type = "BASE TABLE"' },
|
|
709
|
+
{ name: '索引状态', sql: 'SELECT COUNT(*) as status FROM information_schema.statistics WHERE table_schema = DATABASE()' },
|
|
710
|
+
{ name: '磁盘空间', sql: 'SELECT SUM(data_length + index_length) as status FROM information_schema.tables WHERE table_schema = DATABASE()' }
|
|
711
|
+
];
|
|
712
|
+
|
|
713
|
+
const results: any[] = [];
|
|
714
|
+
for (const check of checks) {
|
|
715
|
+
try {
|
|
716
|
+
// 这里应该调用实际的数据库查询
|
|
717
|
+
results.push({
|
|
718
|
+
name: check.name,
|
|
719
|
+
status: 'healthy',
|
|
720
|
+
message: '正常'
|
|
721
|
+
});
|
|
722
|
+
} catch (error: any) {
|
|
723
|
+
results.push({
|
|
724
|
+
name: check.name,
|
|
725
|
+
status: 'error',
|
|
726
|
+
message: error.message
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
addExecutionResult(operation, 'success', { checks: results });
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
function showStatistics() {
|
|
735
|
+
const sql = `
|
|
736
|
+
SELECT
|
|
737
|
+
table_name as '表名',
|
|
738
|
+
table_rows as '记录数',
|
|
739
|
+
ROUND(((data_length + index_length) / 1024 / 1024), 2) as '大小(MB)'
|
|
740
|
+
FROM information_schema.tables
|
|
741
|
+
WHERE table_schema = DATABASE()
|
|
742
|
+
ORDER BY (data_length + index_length) DESC
|
|
743
|
+
`;
|
|
744
|
+
addExecutionResult('数据统计', 'info', { sql: sql, message: '已发送 SQL 查询' });
|
|
745
|
+
emit('execute-sql', sql);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
function showAuditLog() {
|
|
749
|
+
const sql = 'SELECT * FROM mysql.general_log ORDER BY event_time DESC LIMIT 100';
|
|
750
|
+
addExecutionResult('审计日志', 'info', { sql: sql, message: '已发送 SQL 查询' });
|
|
751
|
+
emit('execute-sql', sql);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// 恢复功能
|
|
755
|
+
function showRestoreModal() {
|
|
756
|
+
restoreModalVisible.value = true;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
function closeRestoreModal() {
|
|
760
|
+
restoreModalVisible.value = false;
|
|
761
|
+
selectedFile.value = null;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
function handleFileSelect(event: Event) {
|
|
765
|
+
const target = event.target as HTMLInputElement;
|
|
766
|
+
if (target.files && target.files.length > 0) {
|
|
767
|
+
selectedFile.value = target.files[0] as File;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
async function performRestore() {
|
|
772
|
+
if (!selectedFile.value) return;
|
|
773
|
+
|
|
774
|
+
const operation = '恢复数据库';
|
|
775
|
+
try {
|
|
776
|
+
restoring.value = true;
|
|
777
|
+
const filePath = selectedFile.value.name;
|
|
778
|
+
|
|
779
|
+
const res = await databaseService.restoreDatabase(
|
|
780
|
+
props.connection?.id || '',
|
|
781
|
+
props.database,
|
|
782
|
+
filePath,
|
|
783
|
+
{ dropExisting: restoreOptions.value.dropExisting }
|
|
784
|
+
);
|
|
785
|
+
|
|
786
|
+
addExecutionResult(operation, 'success', res);
|
|
787
|
+
closeRestoreModal();
|
|
788
|
+
} catch (error: any) {
|
|
789
|
+
console.error('恢复失败:', error);
|
|
790
|
+
modal.error(error.msg || error.message || '恢复失败');
|
|
791
|
+
addExecutionResult(operation, 'error', formatError(error));
|
|
792
|
+
} finally {
|
|
793
|
+
restoring.value = false;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// 选择工具
|
|
798
|
+
function selectTool(toolName: string) {
|
|
799
|
+
selectedTool.value = toolName;
|
|
800
|
+
if (toolName === 'sync') {
|
|
801
|
+
initSyncData();
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// 关闭工具
|
|
806
|
+
function closeTool() {
|
|
807
|
+
selectedTool.value = null;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// 获取工具图标
|
|
811
|
+
function getToolIcon(toolName: string) {
|
|
812
|
+
const icons: Record<string, string> = {
|
|
813
|
+
'sync': 'bi-arrow-repeat'
|
|
814
|
+
};
|
|
815
|
+
return icons[toolName] || 'bi-gear';
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// 获取工具标题
|
|
819
|
+
function getToolTitle(toolName: string) {
|
|
820
|
+
const titles: Record<string, string> = {
|
|
821
|
+
'sync': '数据同步'
|
|
822
|
+
};
|
|
823
|
+
return titles[toolName] || '工具';
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// 同步功能 - 初始化数据
|
|
827
|
+
async function initSyncData() {
|
|
828
|
+
try {
|
|
829
|
+
// 加载表列表
|
|
830
|
+
const tablesRes = await databaseService.getTables(props.connection?.id || '', props.database);
|
|
831
|
+
if (tablesRes.ret === 0) {
|
|
832
|
+
tables.value = tablesRes.data || [];
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// 加载已配置的数据库连接列表
|
|
836
|
+
const connRes = await connectionService.getAllConnections();
|
|
837
|
+
if (connRes.ret === 0) {
|
|
838
|
+
connections.value = connRes.data || [];
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// 设置源数据库信息
|
|
842
|
+
syncConfig.value.source.database = props.database;
|
|
843
|
+
|
|
844
|
+
// 默认选择当前连接
|
|
845
|
+
if (props.connection?.id) {
|
|
846
|
+
useExistingConnection.value = true;
|
|
847
|
+
selectedConnectionId.value = props.connection.id;
|
|
848
|
+
}
|
|
849
|
+
} catch (error: any) {
|
|
850
|
+
console.error('加载表列表失败:', error);
|
|
851
|
+
modal.error('加载表列表失败');
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
// 重置同步状态
|
|
856
|
+
function resetSyncState() {
|
|
857
|
+
syncing.value = false;
|
|
858
|
+
tables.value = [];
|
|
859
|
+
databases.value = [];
|
|
860
|
+
useExistingConnection.value = false;
|
|
861
|
+
selectedConnectionId.value = '';
|
|
862
|
+
selectedDatabaseName.value = '';
|
|
863
|
+
syncConfig.value = {
|
|
864
|
+
source: {
|
|
865
|
+
database: '',
|
|
866
|
+
tableName: ''
|
|
867
|
+
},
|
|
868
|
+
target: {
|
|
869
|
+
dbType: 'mysql',
|
|
870
|
+
host: 'localhost',
|
|
871
|
+
port: 3306,
|
|
872
|
+
database: '',
|
|
873
|
+
username: 'root',
|
|
874
|
+
password: '',
|
|
875
|
+
tableName: ''
|
|
876
|
+
},
|
|
877
|
+
options: {
|
|
878
|
+
syncStructure: true,
|
|
879
|
+
syncData: true,
|
|
880
|
+
dropIfExists: false,
|
|
881
|
+
bulkInsert: true,
|
|
882
|
+
overrideExisting: false
|
|
883
|
+
}
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
async function performSync() {
|
|
888
|
+
if (!isSyncFormValid.value) {
|
|
889
|
+
modal.error('请填写完整的同步配置');
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
const operation = '数据同步';
|
|
894
|
+
syncing.value = true;
|
|
895
|
+
|
|
896
|
+
try {
|
|
897
|
+
// 构建同步配置
|
|
898
|
+
let syncData;
|
|
899
|
+
if (useExistingConnection.value) {
|
|
900
|
+
// 使用已配置连接
|
|
901
|
+
syncData = {
|
|
902
|
+
source: {
|
|
903
|
+
database: syncConfig.value.source.database,
|
|
904
|
+
tableName: syncConfig.value.source.tableName
|
|
905
|
+
},
|
|
906
|
+
target: {
|
|
907
|
+
connectionId: selectedConnectionId.value,
|
|
908
|
+
database: selectedDatabaseName.value,
|
|
909
|
+
tableName: syncConfig.value.target.tableName
|
|
910
|
+
},
|
|
911
|
+
options: syncConfig.value.options
|
|
912
|
+
};
|
|
913
|
+
} else {
|
|
914
|
+
// 使用手动配置
|
|
915
|
+
syncData = syncConfig.value;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// 添加同步开始记录
|
|
919
|
+
addExecutionResult(operation, 'info', {
|
|
920
|
+
message: '开始同步数据',
|
|
921
|
+
config: syncData
|
|
922
|
+
});
|
|
923
|
+
|
|
924
|
+
// 执行同步
|
|
925
|
+
const res = await databaseService.syncTable(
|
|
926
|
+
props.connection?.id || '',
|
|
927
|
+
syncData
|
|
928
|
+
);
|
|
929
|
+
|
|
930
|
+
if (res.ret === 0) {
|
|
931
|
+
const tables = res.data?.tables || [];
|
|
932
|
+
let successCount = 0;
|
|
933
|
+
let totalRows = 0;
|
|
934
|
+
|
|
935
|
+
tables.forEach((table: any) => {
|
|
936
|
+
if (table.rowsSynced > 0) {
|
|
937
|
+
successCount++;
|
|
938
|
+
totalRows += table.rowsSynced;
|
|
939
|
+
}
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
addExecutionResult(operation, 'success', {
|
|
943
|
+
message: `数据同步成功,${successCount}/${tables.length} 个表同步完成,共同步 ${totalRows} 行数据`,
|
|
944
|
+
data: res.data
|
|
945
|
+
});
|
|
946
|
+
toast.success(`数据同步成功,${successCount}/${tables.length} 个表同步完成`);
|
|
947
|
+
} else {
|
|
948
|
+
addExecutionResult(operation, 'error', {
|
|
949
|
+
message: res.msg || '同步失败',
|
|
950
|
+
error: res.error
|
|
951
|
+
});
|
|
952
|
+
toast.error(res.msg || '同步失败');
|
|
953
|
+
}
|
|
954
|
+
} catch (error: any) {
|
|
955
|
+
console.error('同步失败:', error);
|
|
956
|
+
addExecutionResult(operation, 'error', formatError(error));
|
|
957
|
+
toast.error(error.msg || error.message || '同步失败');
|
|
958
|
+
} finally {
|
|
959
|
+
syncing.value = false;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
function showScheduleModal() {
|
|
964
|
+
addExecutionResult('定时备份', 'info', { message: '定时备份功能开发中...' });
|
|
965
|
+
}
|
|
966
|
+
</script>
|
|
967
|
+
|
|
968
|
+
<style scoped>
|
|
969
|
+
.db-tools {
|
|
970
|
+
background: white;
|
|
971
|
+
border-radius: 12px;
|
|
972
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
.tools-header {
|
|
976
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
977
|
+
color: white;
|
|
978
|
+
padding: 1rem 1.5rem;
|
|
979
|
+
border-bottom: 1px solid #e2e8f0;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
.tools-title {
|
|
983
|
+
margin: 0;
|
|
984
|
+
font-size: 1.1rem;
|
|
985
|
+
font-weight: 600;
|
|
986
|
+
display: flex;
|
|
987
|
+
align-items: center;
|
|
988
|
+
gap: 0.5rem;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
.tools-content {
|
|
992
|
+
padding: 1.5rem;
|
|
993
|
+
overflow-y: auto;
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
.tool-section {
|
|
997
|
+
margin-bottom: 2rem;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
.section-title {
|
|
1001
|
+
font-size: 0.9rem;
|
|
1002
|
+
font-weight: 600;
|
|
1003
|
+
color: #374151;
|
|
1004
|
+
margin-bottom: 1rem;
|
|
1005
|
+
display: flex;
|
|
1006
|
+
align-items: center;
|
|
1007
|
+
gap: 0.5rem;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
.tool-actions {
|
|
1011
|
+
display: flex;
|
|
1012
|
+
flex-wrap: wrap;
|
|
1013
|
+
gap: 0.5rem;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
.tool-actions .btn {
|
|
1017
|
+
min-width: 120px;
|
|
1018
|
+
display: flex;
|
|
1019
|
+
align-items: center;
|
|
1020
|
+
gap: 0.5rem;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
.modal {
|
|
1024
|
+
background-color: rgba(0, 0, 0, 0.5);
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
.modal-dialog {
|
|
1028
|
+
max-width: 600px;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
.modal-header {
|
|
1032
|
+
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
|
|
1033
|
+
border-bottom: 1px solid #e2e8f0;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
.modal-title {
|
|
1037
|
+
color: #1e293b;
|
|
1038
|
+
font-weight: 600;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
.modal-footer {
|
|
1042
|
+
background: #f8fafc;
|
|
1043
|
+
border-top: 1px solid #e2e8f0;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
/* 工具组件区域 */
|
|
1047
|
+
.tool-component-area {
|
|
1048
|
+
border-top: 1px solid #e2e8f0;
|
|
1049
|
+
background: #f8fafc;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
.component-header {
|
|
1053
|
+
display: flex;
|
|
1054
|
+
justify-content: space-between;
|
|
1055
|
+
align-items: center;
|
|
1056
|
+
padding: 1rem 1.5rem;
|
|
1057
|
+
background: linear-gradient(135deg, #f1f5f9 0%, #f8fafc 100%);
|
|
1058
|
+
border-bottom: 1px solid #e2e8f0;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
.component-title {
|
|
1062
|
+
margin: 0;
|
|
1063
|
+
font-size: 1rem;
|
|
1064
|
+
font-weight: 600;
|
|
1065
|
+
color: #1e293b;
|
|
1066
|
+
display: flex;
|
|
1067
|
+
align-items: center;
|
|
1068
|
+
gap: 0.5rem;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
.tool-component {
|
|
1072
|
+
padding: 1.5rem;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
.sync-component {
|
|
1076
|
+
background: white;
|
|
1077
|
+
border-radius: 0.375rem;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
/* 执行结果区域 */
|
|
1081
|
+
.execution-results {
|
|
1082
|
+
border-top: 1px solid #e2e8f0;
|
|
1083
|
+
background: #f8fafc;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
.results-header {
|
|
1087
|
+
display: flex;
|
|
1088
|
+
justify-content: space-between;
|
|
1089
|
+
align-items: center;
|
|
1090
|
+
padding: 1rem 1.5rem;
|
|
1091
|
+
background: linear-gradient(135deg, #f1f5f9 0%, #f8fafc 100%);
|
|
1092
|
+
border-bottom: 1px solid #e2e8f0;
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
.results-title {
|
|
1096
|
+
margin: 0;
|
|
1097
|
+
font-size: 1rem;
|
|
1098
|
+
font-weight: 600;
|
|
1099
|
+
color: #1e293b;
|
|
1100
|
+
display: flex;
|
|
1101
|
+
align-items: center;
|
|
1102
|
+
gap: 0.5rem;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
.results-content {
|
|
1106
|
+
max-height: 400px;
|
|
1107
|
+
overflow-y: auto;
|
|
1108
|
+
padding: 1rem;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
.no-results {
|
|
1112
|
+
display: flex;
|
|
1113
|
+
flex-direction: column;
|
|
1114
|
+
align-items: center;
|
|
1115
|
+
justify-content: center;
|
|
1116
|
+
padding: 3rem 1rem;
|
|
1117
|
+
color: #94a3b8;
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
.no-results i {
|
|
1121
|
+
font-size: 3rem;
|
|
1122
|
+
margin-bottom: 1rem;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
.no-results p {
|
|
1126
|
+
margin: 0;
|
|
1127
|
+
font-size: 1rem;
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
.result-item {
|
|
1131
|
+
margin-bottom: 0.75rem;
|
|
1132
|
+
border: 1px solid #e2e8f0;
|
|
1133
|
+
border-radius: 8px;
|
|
1134
|
+
background: white;
|
|
1135
|
+
overflow: hidden;
|
|
1136
|
+
transition: box-shadow 0.2s;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
.result-item:hover {
|
|
1140
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
.result-item.result-success {
|
|
1144
|
+
border-left: 4px solid #22c55e;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
.result-item.result-error {
|
|
1148
|
+
border-left: 4px solid #ef4444;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
.result-item.result-info {
|
|
1152
|
+
border-left: 4px solid #3b82f6;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
.result-header {
|
|
1156
|
+
display: flex;
|
|
1157
|
+
justify-content: space-between;
|
|
1158
|
+
align-items: center;
|
|
1159
|
+
padding: 0.75rem 1rem;
|
|
1160
|
+
cursor: pointer;
|
|
1161
|
+
background: white;
|
|
1162
|
+
transition: background 0.2s;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
.result-header:hover {
|
|
1166
|
+
background: #f8fafc;
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
.result-title {
|
|
1170
|
+
display: flex;
|
|
1171
|
+
align-items: center;
|
|
1172
|
+
gap: 0.75rem;
|
|
1173
|
+
flex: 1;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
.result-title i {
|
|
1177
|
+
font-size: 1.1rem;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
.operation-name {
|
|
1181
|
+
font-weight: 600;
|
|
1182
|
+
color: #1e293b;
|
|
1183
|
+
font-size: 0.95rem;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
.operation-time {
|
|
1187
|
+
color: #64748b;
|
|
1188
|
+
font-size: 0.85rem;
|
|
1189
|
+
margin-left: auto;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
.toggle-icon {
|
|
1193
|
+
transition: transform 0.2s;
|
|
1194
|
+
color: #94a3b8;
|
|
1195
|
+
font-size: 0.9rem;
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
.toggle-icon.expanded {
|
|
1199
|
+
transform: rotate(180deg);
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
.result-body {
|
|
1203
|
+
padding: 1rem;
|
|
1204
|
+
background: #fafafa;
|
|
1205
|
+
border-top: 1px solid #e2e8f0;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
.result-body pre {
|
|
1209
|
+
margin: 0;
|
|
1210
|
+
font-size: 0.85rem;
|
|
1211
|
+
line-height: 1.5;
|
|
1212
|
+
max-height: 300px;
|
|
1213
|
+
overflow: auto;
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
.result-body code {
|
|
1217
|
+
font-family: 'Fira Code', 'Consolas', 'Monaco', monospace;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
/* JSON 语法高亮 - 不使用 scoped 以确保 v-html 内容能应用样式 */
|
|
1221
|
+
:deep(.json-key) {
|
|
1222
|
+
color: #d04255;
|
|
1223
|
+
font-weight: 500;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
:deep(.json-string) {
|
|
1227
|
+
color: #22863a;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
:deep(.json-number) {
|
|
1231
|
+
color: #005cc5;
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
:deep(.json-boolean) {
|
|
1235
|
+
color: #d73a49;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
:deep(.json-null) {
|
|
1239
|
+
color: #6f42c1;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
/* 滚动条样式 */
|
|
1243
|
+
.results-content::-webkit-scrollbar,
|
|
1244
|
+
.result-body pre::-webkit-scrollbar {
|
|
1245
|
+
width: 8px;
|
|
1246
|
+
height: 8px;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
.results-content::-webkit-scrollbar-track,
|
|
1250
|
+
.result-body pre::-webkit-scrollbar-track {
|
|
1251
|
+
background: #f1f5f9;
|
|
1252
|
+
border-radius: 4px;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
.results-content::-webkit-scrollbar-thumb,
|
|
1256
|
+
.result-body pre::-webkit-scrollbar-thumb {
|
|
1257
|
+
background: #cbd5e1;
|
|
1258
|
+
border-radius: 4px;
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
.results-content::-webkit-scrollbar-thumb:hover,
|
|
1262
|
+
.result-body pre::-webkit-scrollbar-thumb:hover {
|
|
1263
|
+
background: #94a3b8;
|
|
1264
|
+
}
|
|
817
1265
|
</style>
|